@teammy/server 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // apps/server/ai/snapshots.ts
2
- import { createHash as createHash8 } from "node:crypto";
2
+ import { createHash as createHash9 } from "node:crypto";
3
3
 
4
4
  // apps/server/files.ts
5
- import { createHash as createHash7, randomUUID as randomUUID10 } from "node:crypto";
5
+ import { createHash as createHash8, randomUUID as randomUUID12 } from "node:crypto";
6
6
 
7
7
  // packages/protocol/browse-order.ts
8
8
  var names = new Intl.Collator("zh-CN", { numeric: true, sensitivity: "base" });
@@ -549,7 +549,7 @@ var SiteRepository = class {
549
549
  };
550
550
 
551
551
  // apps/server/workspace-import.ts
552
- import { randomUUID as randomUUID9 } from "node:crypto";
552
+ import { randomUUID as randomUUID11 } from "node:crypto";
553
553
  import { constants as constants2 } from "node:fs";
554
554
  import { lstat, open as open3, readdir } from "node:fs/promises";
555
555
  import { isAbsolute, join as join9 } from "node:path";
@@ -609,7 +609,7 @@ function resolveReference(hub, project, ref, task = null) {
609
609
  }
610
610
 
611
611
  // apps/server/agents/matters.ts
612
- import { randomUUID as randomUUID4 } from "node:crypto";
612
+ import { randomUUID as randomUUID5 } from "node:crypto";
613
613
  import { z as z14 } from "zod";
614
614
 
615
615
  // apps/server/diagnostics/store.ts
@@ -902,11 +902,12 @@ import { join as join3 } from "node:path";
902
902
 
903
903
  // packages/protocol/project-agents.ts
904
904
  import { z as z9 } from "zod";
905
- var AgentTextSchema = z9.object({ text: z9.string().trim().min(1) }).strict();
906
- var AgentReplySchema = z9.object({ body: z9.string().trim().min(1) }).strict();
905
+ var AgentTextSchema = z9.object({ text: z9.string().trim().min(1), requestId: z9.string().min(1).optional() }).strict();
906
+ var AgentReplySchema = z9.object({ body: z9.string().trim().min(1), requestId: z9.string().min(1).optional() }).strict();
907
907
  var OutboxSendSchema = z9.object({
908
908
  to: z9.union([z9.literal("debug"), z9.array(NameSchema).min(1)]),
909
- body: z9.string().trim().min(1)
909
+ body: z9.string().trim().min(1),
910
+ branch: z9.string().trim().min(1).optional()
910
911
  }).strict();
911
912
  var interval = z9.number().int().positive().nullable();
912
913
  var GitProviderSchema = z9.enum(["github", "gongfeng"]);
@@ -961,10 +962,21 @@ var AgentRunSchema = z9.object({
961
962
  error: z9.string().nullable(),
962
963
  createdAt: z9.string()
963
964
  });
965
+ var AgentMessageSchema = z9.object({
966
+ id: z9.string(),
967
+ seq: z9.number(),
968
+ runId: z9.string().nullable(),
969
+ role: z9.enum(["user", "assistant"]),
970
+ author: z9.string(),
971
+ body: z9.string(),
972
+ state: AgentRunSchema.shape.state,
973
+ source: z9.string().nullable(),
974
+ createdAt: z9.string()
975
+ });
964
976
  var AgentChatSummarySchema = z9.object({
965
977
  id: z9.string(),
966
978
  title: z9.string(),
967
- kind: z9.literal("matter").optional(),
979
+ kind: z9.enum(["matter", "notice"]).optional(),
968
980
  status: z9.enum(["active", "waiting", "completed"]).optional(),
969
981
  unread: z9.boolean().optional(),
970
982
  createdAt: z9.string(),
@@ -976,7 +988,13 @@ var AgentChatSchema = z9.object({
976
988
  id: z9.string(),
977
989
  title: z9.string().optional(),
978
990
  runs: z9.array(AgentRunSchema),
979
- kind: z9.literal("matter").optional(),
991
+ messages: z9.array(AgentMessageSchema),
992
+ readCursor: z9.number(),
993
+ redirect: z9.string().nullable().optional(),
994
+ kind: z9.enum(["matter", "notice"]).optional(),
995
+ status: z9.enum(["active", "waiting", "completed"]).optional(),
996
+ pending: z9.object({ members: z9.array(z9.string()), question: z9.string() }).nullable().optional(),
997
+ hold: z9.object({ scope: z9.enum(["all", "writes"]), reason: z9.string() }).passthrough().nullable().optional(),
980
998
  participants: z9.array(z9.string()).optional(),
981
999
  visibility: z9.enum(["project", "participants"]).optional(),
982
1000
  cursor: z9.number()
@@ -989,10 +1007,10 @@ var OutboxMessageSchema = z9.object({
989
1007
  createdAt: z9.string(),
990
1008
  readAt: z9.string().nullable(),
991
1009
  archivedAt: z9.string().nullable(),
1010
+ conversationId: z9.string().nullable(),
992
1011
  recipients: z9.array(
993
1012
  z9.object({
994
- userId: z9.string(),
995
- reply: z9.object({ body: z9.string(), createdAt: z9.string() }).nullable()
1013
+ userId: z9.string()
996
1014
  })
997
1015
  )
998
1016
  });
@@ -1128,6 +1146,11 @@ var SessionCommitSchema = z11.object({
1128
1146
  parentRevision: RevisionSchema,
1129
1147
  blob: BlobRefSchema
1130
1148
  }).strict();
1149
+ var SessionBaselineSchema = z11.object({
1150
+ revision: RevisionSchema,
1151
+ hash: HashSchema.nullable(),
1152
+ cleared: z11.boolean()
1153
+ });
1131
1154
  var SessionInfoSchema = z11.object({
1132
1155
  owner: NameSchema,
1133
1156
  sessionKey: HashSchema,
@@ -1891,8 +1914,10 @@ async function runSessionAnalysisLoop(hub, stop, once = false) {
1891
1914
 
1892
1915
  // apps/server/agents/inputs.ts
1893
1916
  var fingerprint = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
1894
- function enqueueInput(hub, project, text, sourceKey = null, details = {}) {
1917
+ function enqueueInput(hub, project, text, sourceKey = null, details2 = {}) {
1895
1918
  return diagnosticTransaction(hub, () => {
1919
+ if (sourceKey && hub.db.prepare("SELECT 1 FROM reset_event_keys WHERE project_id=? AND source_key=?").get(project, sourceKey))
1920
+ return 0;
1896
1921
  const row = hub.db.prepare(
1897
1922
  `INSERT INTO project_agent_inbox(project_id,task_id,text,source_key,created_at)
1898
1923
  SELECT ?,COALESCE(MAX(task_id),0)+1,?,?,? FROM project_agent_inbox WHERE project_id=?
@@ -1901,18 +1926,23 @@ function enqueueInput(hub, project, text, sourceKey = null, details = {}) {
1901
1926
  const taskId = Number(
1902
1927
  row?.task_id ?? hub.db.prepare("SELECT task_id FROM project_agent_inbox WHERE project_id=? AND source_key=?").get(project, sourceKey)?.task_id
1903
1928
  );
1904
- if (row && Object.keys(details).length)
1905
- hub.db.prepare("INSERT INTO agent_input_details VALUES(?,?,?)").run(project, taskId, JSON.stringify(details));
1906
- const refs = Object.entries(details.references ?? {}).filter(([, target]) => target.kind !== "matter").map(([ref]) => ref);
1907
- if (row && refs.length && refs.length === Object.keys(details.references ?? {}).length) {
1929
+ details2 = {
1930
+ ...details2,
1931
+ origin: details2.origin ?? { kind: "event" },
1932
+ routing: details2.routing ?? { inputKey: sourceKey ?? `inbox:${project}:${taskId}` }
1933
+ };
1934
+ if (row)
1935
+ hub.db.prepare("INSERT INTO agent_input_details VALUES(?,?,?)").run(project, taskId, JSON.stringify(details2));
1936
+ const refs = Object.entries(details2.references ?? {}).filter(([, target]) => target.kind !== "matter").map(([ref]) => ref);
1937
+ if (row && !details2.routing?.excluded?.length && refs.length && refs.length === Object.keys(details2.references ?? {}).length) {
1908
1938
  const targets = hub.db.prepare(
1909
- `SELECT m.conversation_id FROM agent_matters m JOIN agent_matter_sources s ON s.conversation_id=m.conversation_id
1910
- WHERE m.project_id=? AND m.status<>'completed' AND s.ref IN (${refs.map(() => "?").join(",")})
1911
- GROUP BY m.conversation_id HAVING COUNT(DISTINCT s.ref)=?`
1912
- ).all(project, ...refs, refs.length);
1913
- if (targets.length) {
1939
+ `SELECT m.conversation_id,COUNT(DISTINCT s.ref) n FROM agent_matters m JOIN agent_matter_sources s ON s.conversation_id=m.conversation_id
1940
+ WHERE m.project_id=? AND m.status<>'completed' AND s.relation='follow' AND s.ref IN (${refs.map(() => "?").join(",")})
1941
+ GROUP BY m.conversation_id`
1942
+ ).all(project, ...refs);
1943
+ if (targets.length === 1 && Number(targets[0].n) === refs.length) {
1914
1944
  for (const target of targets)
1915
- new Matters(hub).receive(String(target.conversation_id), text, "", details);
1945
+ new Matters(hub).receive(String(target.conversation_id), text, "", details2);
1916
1946
  hub.db.prepare(
1917
1947
  "UPDATE project_agent_inbox SET state='succeeded' WHERE project_id=? AND task_id=?"
1918
1948
  ).run(project, taskId);
@@ -2016,7 +2046,10 @@ function recordTaskChanges(hub, project, additionalText = "", owner = "") {
2016
2046
  "\n\n"
2017
2047
  ),
2018
2048
  null,
2019
- { references }
2049
+ {
2050
+ references,
2051
+ coalesce: { source: `tasks:${Object.keys(references).sort().join(",")}`, mode: "changes" }
2052
+ }
2020
2053
  );
2021
2054
  }
2022
2055
  function recordSessionChange(hub, project, owner, key, revision) {
@@ -2039,7 +2072,7 @@ function recordSessionChange(hub, project, owner, key, revision) {
2039
2072
  [${ref}] ${info?.title || "\u672A\u547D\u540D\u5BF9\u8BDD"}
2040
2073
  ${summary}`,
2041
2074
  `session:${owner}:${key}:${revision}`,
2042
- { references: { [ref]: target } }
2075
+ { references: { [ref]: target }, coalesce: { source: `session:${ref}`, mode: "latest" } }
2043
2076
  );
2044
2077
  }
2045
2078
 
@@ -2190,8 +2223,8 @@ var diagnosticCodes = {
2190
2223
  AI_ANALYSIS_FAILED: ["\u8D44\u6599\u5206\u6790\u672A\u5B8C\u6210", "\u8BF7\u68C0\u67E5\u672C\u6B21\u4F5C\u4E1A\u65E5\u5FD7\u3002"],
2191
2224
  TASK_SOURCE_WARNING: ["\u4EFB\u52A1\u6765\u6E90\u5173\u8054\u5F02\u5E38", "\u8BF7\u68C0\u67E5\u65E5\u5FD7\u4E2D\u7684\u6765\u6E90\u8DEF\u5F84\u53CA\u5BF9\u5E94\u8D44\u6599\u3002"],
2192
2225
  LIFETIME_FAILED: ["\u540E\u53F0\u9879\u76EE\u5206\u6790\u672A\u5B8C\u6210", "\u672C\u6B21\u8F93\u5165\u5DF2\u7ED3\u675F\uFF0C\u540E\u7EED\u8F93\u5165\u5C06\u7EE7\u7EED\u5904\u7406\u3002"],
2193
- CONSTANT_FAILED: ["\u63D0\u767E\u4E8B\u56DE\u7B54\u672A\u5B8C\u6210", "\u7528\u6237\u5DF2\u5728\u804A\u5929\u7A97\u53E3\u6536\u5230\u5931\u8D25\u63D0\u793A\uFF0C\u8BF7\u68C0\u67E5\u8FD0\u884C\u65E5\u5FD7\u3002"],
2194
- INSTANT_FAILED: ["\u63D0\u767E\u4E8B\u56DE\u7B54\u672A\u5B8C\u6210", "\u7528\u6237\u5DF2\u5728\u804A\u5929\u7A97\u53E3\u6536\u5230\u5931\u8D25\u63D0\u793A\uFF0C\u8BF7\u68C0\u67E5\u8FD0\u884C\u65E5\u5FD7\u3002"],
2226
+ CONSTANT_FAILED: ["Tim\u56DE\u7B54\u672A\u5B8C\u6210", "\u7528\u6237\u5DF2\u5728\u804A\u5929\u7A97\u53E3\u6536\u5230\u5931\u8D25\u63D0\u793A\uFF0C\u8BF7\u68C0\u67E5\u8FD0\u884C\u65E5\u5FD7\u3002"],
2227
+ INSTANT_FAILED: ["Tim\u56DE\u7B54\u672A\u5B8C\u6210", "\u7528\u6237\u5DF2\u5728\u804A\u5929\u7A97\u53E3\u6536\u5230\u5931\u8D25\u63D0\u793A\uFF0C\u8BF7\u68C0\u67E5\u8FD0\u884C\u65E5\u5FD7\u3002"],
2195
2228
  REPOSITORY_FAILED: ["\u53C2\u8003\u4ED3\u5E93\u540C\u6B65\u5931\u8D25", "\u8BF7\u68C0\u67E5\u4ED3\u5E93\u8FDE\u63A5\u548C\u51ED\u8BC1\uFF1B\u4EFB\u52A1\u53CA\u4F1A\u8BDD\u5E93\u8D44\u6599\u4ECD\u53EF\u4F7F\u7528\u3002"],
2196
2229
  SANDBOX_FAILED: ["\u9879\u76EE\u8FD0\u884C\u73AF\u5883\u51C6\u5907\u5931\u8D25", "\u8BF7\u68C0\u67E5\u6C99\u7BB1\u4F9D\u8D56\u3001\u6587\u4EF6\u6743\u9650\u53CA\u8FD0\u884C\u65E5\u5FD7\u3002"],
2197
2230
  PR_OPERATION_FAILED: ["PR \u5904\u7406\u672A\u5B8C\u6210", "\u8BF7\u68C0\u67E5\u4ED3\u5E93\u6743\u9650\u548C PR \u63A5\u53E3\uFF1B\u5931\u8D25\u4E0D\u4EE3\u8868\u5BA1\u6838\u901A\u8FC7\u3002"],
@@ -2433,6 +2466,106 @@ function maintainDiagnostics(hub) {
2433
2466
  };
2434
2467
  }
2435
2468
 
2469
+ // apps/server/agents/messages.ts
2470
+ import { createHash as createHash6, randomUUID as randomUUID4 } from "node:crypto";
2471
+ var AgentMessages = class {
2472
+ constructor(hub) {
2473
+ this.hub = hub;
2474
+ }
2475
+ hub;
2476
+ view(row) {
2477
+ return AgentMessageSchema.parse({
2478
+ id: row.id,
2479
+ seq: Number(row.seq),
2480
+ runId: row.run_id,
2481
+ role: row.role,
2482
+ author: row.author,
2483
+ body: row.body,
2484
+ state: row.state,
2485
+ source: row.source,
2486
+ createdAt: row.created_at
2487
+ });
2488
+ }
2489
+ list(conversation, after = 0, limit) {
2490
+ const rows = limit === void 0 ? this.hub.db.prepare("SELECT * FROM agent_messages WHERE conversation_id=? AND seq>? ORDER BY seq").all(conversation, after) : this.hub.db.prepare(
2491
+ "SELECT * FROM agent_messages WHERE conversation_id=? AND seq>? ORDER BY seq LIMIT ?"
2492
+ ).all(conversation, after, limit);
2493
+ return rows.map((row) => this.view(row));
2494
+ }
2495
+ visible(conversation) {
2496
+ return !!this.hub.db.prepare(
2497
+ `SELECT 1 FROM project_agent_conversations c LEFT JOIN agent_contexts x ON x.conversation_id=c.id
2498
+ WHERE c.id=? AND c.role='instant' AND (x.kind IS NULL OR x.kind IN('matter','notice'))`
2499
+ ).get(conversation);
2500
+ }
2501
+ put(input) {
2502
+ if (!this.visible(input.conversation)) return;
2503
+ const date = (/* @__PURE__ */ new Date()).toISOString();
2504
+ const row = this.hub.db.prepare(
2505
+ `INSERT INTO agent_messages(id,conversation_id,run_id,role,author,body,state,source,operation_key,created_at,updated_at)
2506
+ VALUES(?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(operation_key) DO UPDATE SET
2507
+ seq=CASE WHEN agent_messages.state<>'succeeded' AND excluded.state='succeeded' THEN excluded.seq ELSE agent_messages.seq END,
2508
+ body=excluded.body,state=excluded.state,updated_at=excluded.updated_at RETURNING *`
2509
+ ).get(
2510
+ randomUUID4(),
2511
+ input.conversation,
2512
+ input.runId ?? null,
2513
+ input.role,
2514
+ input.author ?? "",
2515
+ input.body,
2516
+ input.state ?? "succeeded",
2517
+ input.source ?? null,
2518
+ input.key,
2519
+ date,
2520
+ date
2521
+ );
2522
+ const value = this.view(row);
2523
+ this.hub.db.prepare("INSERT INTO project_agent_stream(conversation_id,kind,data) VALUES(?,?,?)").run(input.conversation, "message", JSON.stringify(value));
2524
+ return value;
2525
+ }
2526
+ input(conversation, runId, text, author, source) {
2527
+ return this.put({
2528
+ conversation,
2529
+ runId,
2530
+ body: text,
2531
+ author,
2532
+ source,
2533
+ role: "user",
2534
+ key: `input:${runId}`
2535
+ });
2536
+ }
2537
+ answer(conversation, runId, body, state) {
2538
+ return this.put({
2539
+ conversation,
2540
+ runId,
2541
+ body,
2542
+ state,
2543
+ role: "assistant",
2544
+ key: `answer:${runId}`
2545
+ });
2546
+ }
2547
+ delta(runId, text) {
2548
+ this.hub.db.prepare(
2549
+ "UPDATE agent_messages SET body=body||?,state='running',updated_at=? WHERE operation_key=?"
2550
+ ).run(text, (/* @__PURE__ */ new Date()).toISOString(), `answer:${runId}`);
2551
+ }
2552
+ request(key, input, work) {
2553
+ return diagnosticTransaction(this.hub, () => {
2554
+ if (!key) return work();
2555
+ const fingerprint3 = createHash6("sha256").update(JSON.stringify(input)).digest("hex");
2556
+ const old = this.hub.db.prepare("SELECT * FROM agent_message_requests WHERE request_key=?").get(key);
2557
+ if (old) {
2558
+ if (old.fingerprint !== fingerprint3)
2559
+ throw new HubError(409, "OPERATION_MISMATCH", "\u8BE5\u8BF7\u6C42\u6807\u8BC6\u5DF2\u7528\u4E8E\u53E6\u4E00\u6761\u6D88\u606F\u3002");
2560
+ return JSON.parse(String(old.result_json));
2561
+ }
2562
+ const result = work();
2563
+ this.hub.db.prepare("INSERT INTO agent_message_requests VALUES(?,?,?)").run(key, fingerprint3, JSON.stringify(result));
2564
+ return result;
2565
+ });
2566
+ }
2567
+ };
2568
+
2436
2569
  // apps/server/agents/matters.ts
2437
2570
  var DelegateMatterSchema = z14.object({
2438
2571
  ref: z14.string().optional(),
@@ -2440,11 +2573,22 @@ var DelegateMatterSchema = z14.object({
2440
2573
  participants: z14.array(NameSchema).default([]),
2441
2574
  text: z14.string().trim().min(1),
2442
2575
  sources: z14.array(z14.string()).default([]),
2576
+ follows: z14.array(z14.string()).default([]),
2577
+ branch: z14.string().trim().min(1).optional(),
2443
2578
  visibility: z14.enum(["project", "participants"]).optional()
2444
2579
  }).strict();
2445
2580
  var ReportMatterSchema = z14.object({
2581
+ kind: z14.enum(["progress", "coordination", "result"]),
2446
2582
  status: z14.enum(["active", "waiting", "completed"]),
2447
- summary: z14.string().trim().min(1)
2583
+ summary: z14.string().trim().min(1),
2584
+ pending: z14.object({ members: z14.array(NameSchema), question: z14.string().trim().min(1) }).strict().nullable().optional(),
2585
+ request: z14.string().trim().min(1).optional()
2586
+ }).strict().refine((value) => value.kind !== "coordination" || !!value.request, "\u534F\u8C03\u8BF7\u6C42\u9700\u8981\u5177\u4F53 request").refine(
2587
+ (value) => value.status !== "completed" || value.kind === "result",
2588
+ "\u5B8C\u6210\u65F6\u4F7F\u7528 result \u56DE\u62A5"
2589
+ );
2590
+ var MatterHoldSchema = z14.object({
2591
+ hold: z14.object({ scope: z14.enum(["all", "writes"]), reason: z14.string().trim().min(1) }).strict().nullable()
2448
2592
  }).strict();
2449
2593
  var now = () => (/* @__PURE__ */ new Date()).toISOString();
2450
2594
  var Matters = class {
@@ -2462,6 +2606,19 @@ var Matters = class {
2462
2606
  "SELECT member_id FROM agent_matter_members WHERE conversation_id=? ORDER BY member_id"
2463
2607
  ).all(id).map((r) => String(r.member_id));
2464
2608
  }
2609
+ changed(id) {
2610
+ const row = this.hub.db.prepare("SELECT * FROM agent_matters WHERE conversation_id=?").get(id);
2611
+ if (!row) return;
2612
+ new AgentStore(this.hub).event(id, "context", {
2613
+ kind: "matter",
2614
+ title: row.title,
2615
+ status: row.status,
2616
+ visibility: row.visibility,
2617
+ participants: this.members(id),
2618
+ pending: row.pending_json ? JSON.parse(String(row.pending_json)) : null,
2619
+ hold: row.hold_json ? JSON.parse(String(row.hold_json)) : null
2620
+ });
2621
+ }
2465
2622
  requireMember(identity, id) {
2466
2623
  const matter = this.get(identity.workspaceId, id);
2467
2624
  if (!new AgentStore(this.hub).members(identity.workspaceId).includes(identity.memberId) || matter.visibility !== "project" && !this.members(id).includes(identity.memberId))
@@ -2471,57 +2628,83 @@ var Matters = class {
2471
2628
  return Number(
2472
2629
  this.hub.db.prepare(
2473
2630
  `SELECT COUNT(*) n FROM agent_matter_members m JOIN agent_matters a ON a.conversation_id=m.conversation_id
2474
- WHERE a.project_id=? AND m.member_id=? AND EXISTS(SELECT 1 FROM project_agent_stream s WHERE s.conversation_id=a.conversation_id AND s.seq>m.read_seq)`
2631
+ WHERE a.project_id=? AND m.member_id=? AND EXISTS(SELECT 1 FROM agent_messages s WHERE s.conversation_id=a.conversation_id AND s.role='assistant' AND s.state='succeeded' AND s.body<>'' AND s.seq>m.read_seq)`
2475
2632
  ).get(identity.workspaceId, identity.memberId).n
2476
2633
  );
2477
2634
  }
2478
2635
  mark(identity, id, cursor) {
2479
2636
  this.requireMember(identity, id);
2480
2637
  this.hub.db.prepare(
2481
- `UPDATE agent_matter_members SET read_seq=MAX(read_seq,MIN(?,COALESCE((SELECT MAX(seq) FROM project_agent_stream WHERE conversation_id=?),0))) WHERE conversation_id=? AND member_id=?`
2638
+ `UPDATE agent_matter_members SET read_seq=MAX(read_seq,MIN(?,COALESCE((SELECT MAX(seq) FROM agent_messages WHERE conversation_id=?),0))) WHERE conversation_id=? AND member_id=?`
2482
2639
  ).run(cursor, id, id, identity.memberId);
2483
2640
  }
2484
- receive(id, text, actor = "", details = {}, channel) {
2485
- const runId = randomUUID4(), date = now();
2641
+ receive(id, text, actor = "", details2 = {}, channel) {
2642
+ const prior = details2.routing && this.hub.db.prepare("SELECT run_id FROM agent_matter_inputs WHERE conversation_id=? AND input_key=?").get(id, details2.routing.inputKey);
2643
+ if (prior)
2644
+ return new AgentStore(this.hub).runView(
2645
+ this.hub.db.prepare("SELECT * FROM project_agent_runs WHERE id=?").get(prior.run_id)
2646
+ );
2647
+ const runId = randomUUID5(), date = now();
2648
+ details2 = {
2649
+ ...details2,
2650
+ origin: details2.origin ?? {
2651
+ kind: actor || channel ? "human" : "event",
2652
+ ...actor ? { actor } : {}
2653
+ }
2654
+ };
2486
2655
  this.hub.db.prepare(
2487
2656
  "INSERT INTO project_agent_runs(id,conversation_id,text,created_at,updated_at) VALUES(?,?,?,?,?)"
2488
2657
  ).run(runId, id, text, date, date);
2489
2658
  this.hub.db.prepare("INSERT INTO agent_run_actors VALUES(?,?)").run(runId, actor);
2490
2659
  if (channel)
2491
2660
  this.hub.db.prepare("INSERT INTO agent_run_channels VALUES(?,?,?,?)").run(runId, channel.bindingId, channel.externalId, channel.speaker);
2492
- this.hub.db.prepare("INSERT INTO agent_run_details VALUES(?,?)").run(runId, JSON.stringify(details));
2661
+ this.hub.db.prepare("INSERT INTO agent_run_details VALUES(?,?)").run(runId, JSON.stringify(details2));
2662
+ if (details2.routing)
2663
+ this.hub.db.prepare("INSERT INTO agent_matter_inputs VALUES(?,?,?)").run(id, details2.routing.inputKey, runId);
2493
2664
  this.hub.db.prepare("UPDATE agent_matters SET status='active',updated_at=? WHERE conversation_id=?").run(date, id);
2494
2665
  this.hub.db.prepare("UPDATE agent_contexts SET last_active=? WHERE conversation_id=?").run(Date.now(), id);
2495
2666
  const store = new AgentStore(this.hub);
2667
+ const messages = new AgentMessages(this.hub);
2668
+ if (details2.visible !== false && (actor || channel))
2669
+ messages.input(id, runId, text, actor || channel.speaker, channel ? "wecom" : void 0);
2496
2670
  const view = store.runView(
2497
2671
  this.hub.db.prepare("SELECT * FROM project_agent_runs WHERE id=?").get(runId)
2498
2672
  );
2499
2673
  store.event(id, "run", view);
2674
+ messages.answer(id, runId, "", "queued");
2675
+ this.changed(id);
2500
2676
  return view;
2501
2677
  }
2502
- ask(identity, id, text) {
2503
- return diagnosticTransaction(this.hub, () => {
2504
- this.requireMember(identity, id);
2505
- this.invite(id, [identity.memberId]);
2506
- return this.receive(id, text, identity.memberId);
2507
- });
2678
+ ask(identity, id, text, requestId) {
2679
+ this.requireMember(identity, id);
2680
+ return new AgentMessages(this.hub).request(
2681
+ requestId ? `chat:${identity.workspaceId}:${identity.memberId}:${requestId}` : void 0,
2682
+ { chatId: id, text },
2683
+ () => {
2684
+ this.invite(id, [identity.memberId]);
2685
+ return { ...this.receive(id, text, identity.memberId), conversationId: id };
2686
+ }
2687
+ );
2508
2688
  }
2509
2689
  invite(id, members) {
2510
2690
  for (const member of members)
2511
2691
  this.hub.db.prepare(
2512
2692
  "INSERT INTO agent_matter_members(conversation_id,member_id) VALUES(?,?) ON CONFLICT DO NOTHING"
2513
2693
  ).run(id, member);
2694
+ this.changed(id);
2514
2695
  }
2515
- create(project, title2, participants, visibility) {
2516
- const id = randomUUID4();
2696
+ create(project, title2, participants, visibility, existingId) {
2697
+ const id = existingId ?? randomUUID5();
2517
2698
  const channel = this.hub.db.prepare(
2518
2699
  `SELECT c.* FROM wecom_channels c JOIN wecom_config b ON b.bot_id=c.bot_id AND b.enabled=1 WHERE c.project_id=?`
2519
2700
  ).get(project);
2520
2701
  const scope2 = visibility ?? (channel && participants.length ? "project" : "participants");
2521
2702
  this.hub.db.prepare(
2522
- "INSERT INTO project_agent_conversations(id,project_id,role,owner,created_at) VALUES(?,?,'instant','',?)"
2703
+ "INSERT INTO project_agent_conversations(id,project_id,role,owner,created_at) VALUES(?,?,'instant','',?) ON CONFLICT(id) DO NOTHING"
2523
2704
  ).run(id, project, now());
2524
- this.hub.db.prepare("INSERT INTO agent_contexts VALUES(?,'matter',?,?,0)").run(id, id, Date.now());
2705
+ this.hub.db.prepare(
2706
+ "INSERT INTO agent_contexts VALUES(?,'matter',?,?,0) ON CONFLICT(conversation_id) DO UPDATE SET kind='matter',scope_key=excluded.scope_key"
2707
+ ).run(id, id, Date.now());
2525
2708
  const ref = reference(this.hub, project, { kind: "matter", id });
2526
2709
  this.hub.db.prepare(
2527
2710
  "INSERT INTO agent_matters(conversation_id,project_id,ref,title,visibility,created_at,updated_at) VALUES(?,?,?,?,?,?,?)"
@@ -2533,17 +2716,66 @@ var Matters = class {
2533
2716
  ).run(id, channel.id, channel.bot_id, channel.topic_id);
2534
2717
  return { id, ref };
2535
2718
  }
2719
+ details(run) {
2720
+ const batch = this.hub.db.prepare("SELECT details_json FROM agent_run_batches WHERE run_id=?").get(run.id);
2721
+ if (batch) return JSON.parse(String(batch.details_json));
2722
+ const row = this.hub.db.prepare("SELECT details_json FROM agent_run_details WHERE run_id=?").get(run.id);
2723
+ return row ? JSON.parse(String(row.details_json)) : inputDetails(this.hub, run.project_id, run.task_id);
2724
+ }
2725
+ context(run) {
2726
+ if (run.context_kind === "matter") {
2727
+ const row = this.get(run.project_id, run.conversation_id);
2728
+ return `\u5F53\u524D\u4E8B\u9879\u76EE\u6807\u4E0E\u72B6\u6001\uFF08\u4EE5\u6570\u636E\u5E93\u4E3A\u51C6\uFF09\uFF1A
2729
+ ${JSON.stringify({
2730
+ ref: row.ref,
2731
+ title: row.title,
2732
+ goal: row.goal,
2733
+ status: row.status,
2734
+ summary: row.summary,
2735
+ pending: row.pending_json ? JSON.parse(String(row.pending_json)) : null,
2736
+ hold: row.hold_json ? JSON.parse(String(row.hold_json)) : null
2737
+ })}`;
2738
+ }
2739
+ if (run.role !== "lifetime") return "";
2740
+ const refs = Object.keys(this.details(run).references ?? {});
2741
+ const matches = this.hub.db.prepare(
2742
+ `SELECT DISTINCT m.ref,m.title,m.status,m.summary,m.pending_json,m.hold_json FROM agent_matters m
2743
+ LEFT JOIN agent_matter_sources s ON s.conversation_id=m.conversation_id
2744
+ WHERE m.project_id=? AND ${refs.length ? `(s.ref IN (${refs.map(() => "?").join(",")}) OR m.ref IN (${refs.map(() => "?").join(",")}))` : "m.status<>'completed'"}
2745
+ ORDER BY m.updated_at DESC LIMIT 20`
2746
+ ).all(run.project_id, ...refs.length ? [...refs, ...refs] : []);
2747
+ return matches.length ? `\u5F53\u524D\u76F8\u5173\u4E8B\u9879\u6982\u89C8\uFF08\u66F4\u591A\u5185\u5BB9\u7528 query_matters/read_matter \u67E5\u8BE2\uFF0C\u4E0D\u4F9D\u8D56\u5386\u53F2\u8BB0\u5FC6\uFF09\uFF1A
2748
+ ${JSON.stringify(matches)}` : "";
2749
+ }
2536
2750
  delegate(run, input, operation) {
2537
2751
  return diagnosticTransaction(this.hub, () => {
2538
- const prior = this.hub.db.prepare("SELECT result_json FROM agent_matter_operations WHERE operation_key=?").get(operation);
2752
+ const details2 = this.details(run);
2753
+ const inputKey = (details2.routing?.inputKey ?? `run:${run.id}`) + (details2.routing?.excluded?.length ? `:returned:${[...details2.routing.excluded].sort().join(",")}` : "");
2754
+ const branch = input.branch ?? "";
2755
+ const prior = this.hub.db.prepare(
2756
+ "SELECT result_json FROM agent_event_routes WHERE project_id=? AND input_key=? AND branch=?"
2757
+ ).get(run.project_id, inputKey, branch);
2539
2758
  if (prior) return JSON.parse(String(prior.result_json));
2540
2759
  const store = new AgentStore(this.hub);
2541
2760
  const participants = [...new Set(input.participants)], valid = new Set(store.members(run.project_id));
2542
2761
  if (participants.some((member) => !valid.has(member)))
2543
2762
  throw new HubError(400, "MATTER_MEMBER_INVALID", "\u53C2\u4E0E\u4EBA\u5FC5\u987B\u662F\u672C\u9879\u76EE\u6709\u6548\u6210\u5458\u3002");
2763
+ const notice = details2.notification;
2764
+ const linked = notice && this.hub.db.prepare(
2765
+ "SELECT target_id FROM agent_notice_threads WHERE conversation_id=? AND user_id=?"
2766
+ ).get(notice.conversationId, notice.memberId)?.target_id;
2767
+ if (linked) input = { ...input, ref: String(linked) };
2768
+ const promote = !input.ref && notice && participants.every((member) => member === notice.memberId) && input.visibility !== "project" ? notice.conversationId : void 0;
2769
+ if (notice && !participants.includes(notice.memberId)) participants.push(notice.memberId);
2544
2770
  let id, ref;
2545
2771
  if (input.ref) {
2546
2772
  const current = this.get(run.project_id, input.ref);
2773
+ if (details2.routing?.excluded?.includes(String(current.conversation_id)))
2774
+ throw new HubError(
2775
+ 409,
2776
+ "MATTER_INPUT_RETURNED",
2777
+ "\u8FD9\u4E2A\u4E8B\u9879\u5DF2\u9000\u56DE\u6B64\u8F93\u5165\uFF0C\u8BF7\u9009\u62E9\u5176\u4ED6\u4E8B\u9879\u6216\u65B0\u5EFA\u3002"
2778
+ );
2547
2779
  if (input.visibility && input.visibility !== current.visibility)
2548
2780
  throw new HubError(
2549
2781
  400,
@@ -2555,55 +2787,237 @@ var Matters = class {
2555
2787
  } else {
2556
2788
  if (!input.title)
2557
2789
  throw new HubError(400, "MATTER_TITLE_REQUIRED", "\u65B0\u5EFA\u4E8B\u9879\u9700\u8981\u7B80\u77ED\u6807\u9898\u3002");
2558
- ({ id, ref } = this.create(run.project_id, input.title, participants, input.visibility));
2790
+ ({ id, ref } = this.create(
2791
+ run.project_id,
2792
+ input.title,
2793
+ participants,
2794
+ promote ? "participants" : input.visibility,
2795
+ promote
2796
+ ));
2797
+ this.hub.db.prepare("UPDATE agent_matters SET goal=? WHERE conversation_id=?").run(input.text, id);
2559
2798
  }
2560
2799
  this.invite(id, participants);
2561
2800
  const references = {};
2562
- for (const source of input.sources) {
2563
- references[source] = resolveReference(this.hub, run.project_id, source, run.task_id);
2564
- this.hub.db.prepare("INSERT INTO agent_matter_sources VALUES(?,?) ON CONFLICT DO NOTHING").run(id, source);
2801
+ for (const source of [.../* @__PURE__ */ new Set([...input.sources, ...input.follows])]) {
2802
+ references[source] = details2.references?.[source] ?? resolveReference(this.hub, run.project_id, source, run.task_id);
2803
+ this.hub.db.prepare(
2804
+ `INSERT INTO agent_matter_sources(conversation_id,ref,relation) VALUES(?,?,?)
2805
+ ON CONFLICT(conversation_id,ref) DO UPDATE SET relation=CASE WHEN excluded.relation='follow' THEN 'follow' ELSE relation END`
2806
+ ).run(id, source, input.follows.includes(source) ? "follow" : "evidence");
2565
2807
  }
2566
- const queued = this.receive(id, input.text, "", {
2567
- references,
2568
- audit: { sourceRunId: run.id }
2569
- });
2570
- const result = { ref, conversationId: id, runId: queued.id };
2571
- this.hub.db.prepare("INSERT INTO agent_matter_operations VALUES(?,?)").run(operation, JSON.stringify(result));
2808
+ let runId;
2809
+ if (promote && notice) {
2810
+ runId = notice.runId;
2811
+ this.hub.db.prepare("UPDATE agent_run_details SET details_json=? WHERE run_id=?").run(
2812
+ JSON.stringify({
2813
+ references,
2814
+ origin: { kind: "human", actor: notice.memberId },
2815
+ routing: { inputKey },
2816
+ audit: { sourceRunId: run.id }
2817
+ }),
2818
+ runId
2819
+ );
2820
+ const siblings = this.hub.db.prepare(
2821
+ `SELECT task_id FROM agent_input_details WHERE project_id=?
2822
+ AND json_extract(details_json,'$.notification.conversationId')=?`
2823
+ ).all(run.project_id, promote);
2824
+ for (const item of siblings)
2825
+ if (item.task_id !== run.task_id) {
2826
+ this.hub.db.prepare(
2827
+ "UPDATE project_agent_inbox SET state='succeeded' WHERE project_id=? AND task_id=? AND state='queued'"
2828
+ ).run(run.project_id, item.task_id);
2829
+ this.hub.db.prepare(
2830
+ `UPDATE project_agent_runs SET state='succeeded' WHERE task_id=? AND state='queued'
2831
+ AND conversation_id IN(SELECT id FROM project_agent_conversations WHERE project_id=? AND role='lifetime')`
2832
+ ).run(item.task_id, run.project_id);
2833
+ }
2834
+ } else {
2835
+ const original = notice ? "" : run.text;
2836
+ const text = original && original !== input.text ? `${input.text}
2837
+
2838
+ \u539F\u59CB\u8F93\u5165\uFF1A
2839
+ ${original}` : input.text;
2840
+ runId = this.receive(id, text, "", {
2841
+ references,
2842
+ origin: details2.origin ?? { kind: "event" },
2843
+ routing: { inputKey: `${inputKey}:${branch}`, excluded: details2.routing?.excluded },
2844
+ visible: false,
2845
+ audit: { sourceRunId: run.id }
2846
+ }).id;
2847
+ }
2848
+ if (notice) {
2849
+ this.hub.db.prepare(
2850
+ "UPDATE agent_notice_threads SET target_id=? WHERE conversation_id=? AND user_id=?"
2851
+ ).run(id, notice.conversationId, notice.memberId);
2852
+ if (!promote) {
2853
+ this.hub.db.prepare(
2854
+ "UPDATE project_agent_runs SET state='succeeded',answer='',updated_at=? WHERE id=?"
2855
+ ).run(now(), notice.runId);
2856
+ new AgentMessages(this.hub).answer(notice.conversationId, notice.runId, "", "succeeded");
2857
+ store.event(notice.conversationId, "context", { redirect: id });
2858
+ }
2859
+ }
2860
+ const result = { ref, conversationId: id, runId };
2861
+ this.hub.db.prepare("INSERT INTO agent_event_routes VALUES(?,?,?,?,?)").run(run.project_id, inputKey, branch, id, JSON.stringify(result));
2862
+ this.hub.db.prepare("INSERT OR IGNORE INTO agent_matter_operations VALUES(?,?)").run(operation, JSON.stringify(result));
2572
2863
  return result;
2573
2864
  });
2574
2865
  }
2575
2866
  report(run, input, operation) {
2576
2867
  return diagnosticTransaction(this.hub, () => {
2577
- const prior = this.hub.db.prepare("SELECT result_json FROM agent_matter_operations WHERE operation_key=?").get(operation);
2578
- if (prior) return JSON.parse(String(prior.result_json));
2868
+ const old = this.hub.db.prepare("SELECT * FROM agent_matter_reports WHERE operation_key=?").get(operation);
2869
+ if (old && old.state !== "abandoned")
2870
+ return {
2871
+ ref: this.get(run.project_id, run.conversation_id).ref,
2872
+ status: old.status,
2873
+ taskId: old.task_id,
2874
+ recorded: true
2875
+ };
2579
2876
  const matter = this.get(run.project_id, run.conversation_id);
2580
- const queued = this.hub.db.prepare("SELECT 1 FROM project_agent_runs WHERE conversation_id=? AND state='queued'").get(run.conversation_id);
2581
- const status = queued ? "active" : input.status;
2582
- this.hub.db.prepare("UPDATE agent_matters SET status=?,summary=?,updated_at=? WHERE conversation_id=?").run(status, input.summary, now(), run.conversation_id);
2583
- let taskId = null;
2584
- if (matter.summary !== input.summary || matter.status !== status) {
2585
- const ref = String(matter.ref);
2586
- taskId = enqueueInput(
2587
- this.hub,
2588
- run.project_id,
2589
- `[${ref}] ${matter.title} \xB7 ${{ active: "\u8FDB\u5C55", waiting: "\u9700\u8981\u534F\u8C03", completed: "\u5DF2\u5B8C\u6210" }[status]}
2590
- ${input.summary}`,
2591
- operation,
2592
- {
2593
- references: { [ref]: { kind: "matter", id: run.conversation_id } },
2594
- audit: { sourceRunId: run.id }
2595
- }
2877
+ if (input.pending?.members.some((member) => !this.members(run.conversation_id).includes(member)))
2878
+ throw new HubError(400, "MATTER_MEMBER_INVALID", "\u5F85\u786E\u8BA4\u5BF9\u8C61\u5FC5\u987B\u662F\u4E8B\u9879\u53C2\u4E0E\u4EBA\u3002");
2879
+ if (input.status === "completed" && (input.pending || input.pending === void 0 && matter.pending_json || matter.hold_json))
2880
+ throw new HubError(
2881
+ 409,
2882
+ "MATTER_NOT_FINISHED",
2883
+ "\u4ECD\u6709\u5F85\u786E\u8BA4\u95EE\u9898\u6216\u660E\u786E\u6682\u505C\u8981\u6C42\uFF0C\u4E0D\u80FD\u62A5\u544A\u5B8C\u6210\u3002"
2596
2884
  );
2597
- }
2598
- const result = { ref: matter.ref, status, taskId };
2599
- this.hub.db.prepare("INSERT INTO agent_matter_operations VALUES(?,?)").run(operation, JSON.stringify(result));
2600
- return result;
2885
+ if (old) this.hub.db.prepare("DELETE FROM agent_matter_reports WHERE id=?").run(old.id);
2886
+ const id = randomUUID5();
2887
+ this.hub.db.prepare(
2888
+ "INSERT INTO agent_matter_reports(id,conversation_id,run_id,kind,status,summary,details_json,created_at,operation_key) VALUES(?,?,?,?,?,?,?,?,?)"
2889
+ ).run(
2890
+ id,
2891
+ run.conversation_id,
2892
+ run.id,
2893
+ input.kind,
2894
+ input.status,
2895
+ input.summary,
2896
+ JSON.stringify(input),
2897
+ now(),
2898
+ operation
2899
+ );
2900
+ if (input.kind !== "result") this.commitReport(run, id);
2901
+ const current = this.hub.db.prepare("SELECT * FROM agent_matter_reports WHERE id=?").get(id);
2902
+ return {
2903
+ ref: matter.ref,
2904
+ status: input.status,
2905
+ taskId: current.task_id,
2906
+ recorded: true,
2907
+ pending: current.state === "pending"
2908
+ };
2909
+ });
2910
+ }
2911
+ commitReports(run, answer) {
2912
+ const pending = this.hub.db.prepare(
2913
+ "SELECT id FROM agent_matter_reports WHERE run_id=? AND state='pending' ORDER BY rowid"
2914
+ ).all(run.id);
2915
+ for (const row of pending.slice(0, -1))
2916
+ this.hub.db.prepare("UPDATE agent_matter_reports SET state='superseded' WHERE id=?").run(row.id);
2917
+ if (pending.length) {
2918
+ const id = String(pending.at(-1).id);
2919
+ const row = this.hub.db.prepare("SELECT status FROM agent_matter_reports WHERE id=?").get(id);
2920
+ const matter = this.get(run.project_id, run.conversation_id);
2921
+ if (row.status === "completed" && this.members(run.conversation_id).length && !answer.trim())
2922
+ throw new HubError(409, "MATTER_ANSWER_REQUIRED", "\u5B8C\u6210\u59D4\u6258\u65F6\u9700\u8981\u7ED9\u53C2\u4E0E\u4EBA\u4E00\u6761\u53EF\u89C1\u7ED3\u8BBA\u3002");
2923
+ if (row.status === "completed" && matter.hold_json)
2924
+ throw new HubError(409, "MATTER_NOT_FINISHED", "\u4E8B\u9879\u4ECD\u6709\u660E\u786E\u6682\u505C\u8981\u6C42\uFF0C\u4E0D\u80FD\u62A5\u544A\u5B8C\u6210\u3002");
2925
+ this.commitReport(run, id);
2926
+ }
2927
+ }
2928
+ abandonDrafts(runId) {
2929
+ this.hub.db.prepare(
2930
+ "UPDATE agent_matter_reports SET state='abandoned' WHERE run_id=? AND state='pending'"
2931
+ ).run(runId);
2932
+ this.hub.db.prepare("DELETE FROM agent_answer_drafts WHERE run_id=?").run(runId);
2933
+ this.hub.db.prepare("DELETE FROM agent_run_notifications WHERE run_id=?").run(runId);
2934
+ }
2935
+ commitReport(run, id) {
2936
+ const row = this.hub.db.prepare("SELECT * FROM agent_matter_reports WHERE id=? AND state='pending'").get(id);
2937
+ if (!row) return;
2938
+ const input = JSON.parse(String(row.details_json));
2939
+ const matter = this.get(run.project_id, run.conversation_id);
2940
+ const queued = this.hub.db.prepare("SELECT 1 FROM project_agent_runs WHERE conversation_id=? AND state='queued'").get(run.conversation_id);
2941
+ const status = queued && input.status === "completed" ? "active" : input.status;
2942
+ this.hub.db.prepare(
2943
+ "UPDATE agent_matters SET status=?,summary=?,pending_json=?,revision=revision+1,updated_at=? WHERE conversation_id=?"
2944
+ ).run(
2945
+ status,
2946
+ input.summary,
2947
+ input.pending === void 0 ? matter.pending_json : input.pending === null ? null : JSON.stringify(input.pending),
2948
+ now(),
2949
+ run.conversation_id
2950
+ );
2951
+ let taskId = null;
2952
+ const previous = this.hub.db.prepare(
2953
+ "SELECT details_json FROM agent_matter_reports WHERE conversation_id=? AND kind=? AND state='committed' ORDER BY rowid DESC LIMIT 1"
2954
+ ).get(run.conversation_id, input.kind);
2955
+ if (input.kind !== "progress" && previous?.details_json !== row.details_json) {
2956
+ const ref = String(matter.ref);
2957
+ taskId = enqueueInput(
2958
+ this.hub,
2959
+ run.project_id,
2960
+ `[${ref}] ${matter.title} \xB7 ${input.kind === "coordination" ? "\u9700\u8981\u534F\u8C03" : status === "completed" ? "\u5DF2\u5B8C\u6210" : "\u91CD\u8981\u7ED3\u8BBA"}
2961
+ ${input.summary}${input.request ? "\n\u534F\u8C03\u8BF7\u6C42\uFF1A" + input.request : ""}`,
2962
+ `matter-report:${id}`,
2963
+ {
2964
+ references: { [ref]: { kind: "matter", id: run.conversation_id } },
2965
+ origin: { kind: "report" },
2966
+ audit: { sourceRunId: run.id }
2967
+ }
2968
+ );
2969
+ }
2970
+ this.hub.db.prepare("UPDATE agent_matter_reports SET state='committed',task_id=? WHERE id=?").run(taskId, id);
2971
+ this.changed(run.conversation_id);
2972
+ }
2973
+ hold(run, value) {
2974
+ const details2 = this.details(run);
2975
+ if (details2.origin?.kind !== "human")
2976
+ throw new HubError(403, "MATTER_HOLD_SOURCE", "\u6682\u505C\u6216\u6062\u590D\u9700\u8981\u5F53\u524D\u7528\u6237\u7684\u660E\u786E\u8981\u6C42\u3002");
2977
+ const hold = value.hold ? { ...value.hold, sourceRunId: run.id, actor: details2.origin.actor ?? run.owner } : null;
2978
+ this.hub.db.prepare(
2979
+ "UPDATE agent_matters SET hold_json=?,revision=revision+1,updated_at=? WHERE conversation_id=?"
2980
+ ).run(hold ? JSON.stringify(hold) : null, now(), run.conversation_id);
2981
+ this.changed(run.conversation_id);
2982
+ return { hold };
2983
+ }
2984
+ assertWritable(run) {
2985
+ if (run.context_kind !== "matter") return;
2986
+ if (this.get(run.project_id, run.conversation_id).hold_json)
2987
+ throw new HubError(
2988
+ 409,
2989
+ "MATTER_ON_HOLD",
2990
+ "\u7528\u6237\u8981\u6C42\u6682\u7F13\u64CD\u4F5C\uFF0C\u8BF7\u5148\u5728\u5F53\u524D\u4E8B\u9879\u6F84\u6E05\u6216\u53D6\u5F97\u660E\u786E\u6062\u590D\u8981\u6C42\u3002"
2991
+ );
2992
+ }
2993
+ handback(run, reason) {
2994
+ return diagnosticTransaction(this.hub, () => {
2995
+ const prior = this.hub.db.prepare("SELECT task_id FROM agent_input_handbacks WHERE run_id=?").get(run.id);
2996
+ if (prior) return { taskId: Number(prior.task_id) };
2997
+ this.abandonDrafts(run.id);
2998
+ const details2 = this.details(run), current = this.get(run.project_id, run.conversation_id);
2999
+ const routing = {
3000
+ inputKey: details2.routing?.inputKey ?? `run:${run.id}`,
3001
+ excluded: [.../* @__PURE__ */ new Set([...details2.routing?.excluded ?? [], run.conversation_id])]
3002
+ };
3003
+ const taskId = enqueueInput(
3004
+ this.hub,
3005
+ run.project_id,
3006
+ `[${current.ref}] ${current.title} \u9000\u56DE\u8F93\u5165
3007
+ \u539F\u56E0\uFF1A${reason}
3008
+ \u539F\u8F93\u5165\uFF1A
3009
+ ${run.text}`,
3010
+ `handback:${run.id}`,
3011
+ { ...details2, routing, visible: false }
3012
+ );
3013
+ this.hub.db.prepare("INSERT INTO agent_input_handbacks VALUES(?,?,?,?)").run(run.id, run.conversation_id, reason, taskId);
3014
+ return { taskId };
2601
3015
  });
2602
3016
  }
2603
3017
  };
2604
3018
 
2605
- // apps/server/agents/store.ts
2606
- import { randomUUID as randomUUID5 } from "node:crypto";
3019
+ // apps/server/agents/notices.ts
3020
+ import { randomUUID as randomUUID6 } from "node:crypto";
2607
3021
 
2608
3022
  // apps/server/agents/prompts.ts
2609
3023
  var materials = `## \u8D44\u6599\u4E0E\u5224\u65AD\u4F9D\u636E
@@ -2614,7 +3028,7 @@ var queryTools = `\u53EF\u81EA\u4E3B\u4F7F\u7528\u6587\u4EF6\u5DE5\u5177\u3001Gi
2614
3028
  \u8054\u7CFB\u6210\u5458\u524D\u53EF\u7528 list_members \u67E5\u8BE2\u6709\u6548\u6210\u5458 ID\uFF1B\u5BBF\u4E3B\u6821\u9A8C\u5B9E\u9645\u6295\u9012\u5BF9\u8C61\u3002\u8D44\u6599\u77ED\u5F15\u7528\u901A\u8FC7 read_reference \u56DE\u67E5\u3002`;
2615
3029
  var citations = `\u5F15\u7528\u9879\u76EE\u8D44\u6599\u65F6\u5199\u6210 [\u8D44\u6599\u540D\u79F0](\u94FE\u63A5)\uFF0C\u540D\u79F0\u5E94\u4FBF\u4E8E\u7406\u89E3\uFF1A\u804A\u5929\u548C outbox \u4F7F\u7528\u5DE5\u5177\u8FD4\u56DE\u7684 url\uFF0CPR \u8BC4\u8BBA\u4F7F\u7528 publicUrl\u3002\u4FDD\u7559\u5B8C\u6574\u94FE\u63A5\u4E2D\u7684\u7248\u672C\u548C\u6D88\u606F\u5B9A\u4F4D\u4FE1\u606F\u3002
2616
3030
  \u5F15\u7528\u6C99\u7BB1\u6587\u4EF6\u524D\uFF0C\u5148\u8C03\u7528 get_file_link \u83B7\u53D6\u7F51\u9875\u94FE\u63A5\uFF1B\u4E0D\u8981\u628A /project/... \u672C\u5730\u8DEF\u5F84\u5F53\u7F51\u9875\u94FE\u63A5\u53D1\u9001\u3002`;
2617
- var voice = "\u5BF9\u5916\u8868\u8FBE\u76F4\u63A5\u8FDB\u5165\u4E3B\u9898\uFF1B\u786E\u9700\u81EA\u6211\u4ECB\u7ECD\u65F6\u7EDF\u4E00\u4F7F\u7528\u300C\u6211\u662F\u63D0\u767E\u4E8B\u300D\uFF0C\u4E0D\u4ECB\u7ECD lifetime\u3001instant \u7B49\u5185\u90E8\u8FD0\u884C\u89D2\u8272\u3002";
3031
+ var voice = "\u5BF9\u5916\u8868\u8FBE\u76F4\u63A5\u8FDB\u5165\u4E3B\u9898\uFF1B\u786E\u9700\u81EA\u6211\u4ECB\u7ECD\u65F6\u7EDF\u4E00\u4F7F\u7528\u300C\u6211\u662FTim\u300D\uFF0C\u4E0D\u4ECB\u7ECD lifetime\u3001instant \u7B49\u5185\u90E8\u8FD0\u884C\u89D2\u8272\u3002";
2618
3032
  var expression = `## \u8868\u8FBE\u98CE\u683C
2619
3033
  ${voice}
2620
3034
  \u9ED8\u8BA4\u7B80\u77ED\uFF0C\u5148\u7ED9\u7ED3\u8BBA\uFF0C\u518D\u8865\u5145\u5F71\u54CD\u7406\u89E3\u6216\u884C\u52A8\u7684\u5FC5\u8981\u4FE1\u606F\u3002\u627F\u63A5\u5DF2\u6709\u5BF9\u8BDD\uFF0C\u7701\u53BB\u5BA2\u5957\u3001\u91CD\u590D\u80CC\u666F\u548C\u91CD\u590D\u603B\u7ED3\uFF1B\u590D\u6742\u95EE\u9898\u6216\u7528\u6237\u8981\u6C42\u8BE6\u89E3\u65F6\u518D\u5C55\u5F00\u3002
@@ -2628,7 +3042,7 @@ var audience = `## \u6C9F\u901A\u8303\u56F4
2628
3042
  \u56DE\u590D\u56F4\u7ED5\u539F\u95EE\u9898\u5C55\u5F00\uFF0C\u4E0D\u56E0\u5171\u4EAB\u9879\u76EE\u4E0A\u4E0B\u6587\u6DF7\u5165\u5176\u4ED6\u4EBA\u7684\u4E8B\u9879\u3002`;
2629
3043
  function projectPrompt(role) {
2630
3044
  return role === "lifetime" ? `## \u5B9A\u4F4D\u4E0E\u804C\u8D23
2631
- \u4F60\u662F Teammy \u7684\u9879\u76EE\u534F\u4F5C\u52A9\u624B\u300C\u63D0\u767E\u4E8B\u300D\uFF0C\u6301\u7EED\u63A5\u6536\u9879\u76EE\u53D8\u5316\u548C\u7528\u6237\u59D4\u6258\uFF0C\u5173\u6CE8\u5F71\u54CD\u56E2\u961F\u534F\u4F5C\u65B9\u5411\u3001\u6295\u5165\u4E0E\u7ED3\u679C\u7684\u91CD\u8981\u4E8B\u9879\u3002
3045
+ \u4F60\u662F Teammy \u7684\u9879\u76EE\u534F\u4F5C\u52A9\u624B\u300CTim\u300D\uFF0C\u6301\u7EED\u63A5\u6536\u9879\u76EE\u53D8\u5316\u548C\u7528\u6237\u59D4\u6258\uFF0C\u5173\u6CE8\u5F71\u54CD\u56E2\u961F\u534F\u4F5C\u65B9\u5411\u3001\u6295\u5165\u4E0E\u7ED3\u679C\u7684\u91CD\u8981\u4E8B\u9879\u3002
2632
3046
 
2633
3047
  ${materials}
2634
3048
 
@@ -2644,11 +3058,11 @@ ${audience}
2644
3058
  ## \u534F\u8C03\u4E0E\u4E8B\u9879
2645
3059
  ${queryTools}
2646
3060
  \u4F60\u8D1F\u8D23\u9879\u76EE\u7406\u89E3\u548C\u534F\u8C03\u3002\u666E\u901A\u8FDB\u5C55\u9759\u9ED8\u5904\u7406\uFF1B\u7B80\u5355\u67E5\u8BE2\u548C\u5224\u65AD\u53EF\u76F4\u63A5\u5B8C\u6210\u3002\u9700\u8981\u6DF1\u5165\u5206\u6790\u3001\u6301\u7EED\u8DDF\u8FDB\u3001\u8054\u7CFB\u6210\u5458\u6216\u5BA1\u6838 PR \u65F6\uFF0C\u4F7F\u7528 delegate_matter \u4EA4\u7ED9\u4E8B\u9879 Agent\uFF0C\u4E0D\u628A\u5177\u4F53\u5DE5\u4F5C\u548C\u591A\u8F6E\u8BA8\u8BBA\u7559\u5728\u672C\u4F1A\u8BDD\u3002
2647
- \u5148\u901A\u8FC7 query_matters \u67E5\u627E\u5DF2\u6709\u76F8\u5173\u4E8B\u9879\uFF1B\u540E\u7EED\u53D8\u5316\u7528 ref \u7EE7\u7EED\u540C\u4E00\u4E8B\u9879\uFF0C\u4E0D\u9010\u6761\u65B0\u5EFA\u3002\u65B0\u5EFA\u65F6\u63D0\u4F9B\u7B80\u77ED\u6807\u9898\u3001\u76EE\u6807\u3001\u5FC5\u8981\u4F9D\u636E\u3001\u7528\u6237\u660E\u786E\u6388\u6743\u4E0E\u8FB9\u754C\uFF1Bsources \u4F7F\u7528\u4E8B\u4EF6\u7ED9\u51FA\u7684\u77ED\u5F15\u7528\u3002\u9700\u8981\u6C9F\u901A\u65F6\u9080\u8BF7\u76F8\u5173\u6210\u5458\uFF0C\u4ED6\u4EEC\u53EF\u5728\u540C\u4E00\u7F51\u9875\u4F1A\u8BDD\u4E2D\u591A\u8F6E\u53D1\u8A00\u3002text \u4F1A\u5BF9\u53C2\u4E0E\u4EBA\u53EF\u89C1\uFF0C\u4E0D\u590D\u5236\u79C1\u4EBA\u804A\u5929\uFF1B\u65B0\u589E\u53C2\u4E0E\u4EBA\u610F\u5473\u7740\u5206\u4EAB\u8BE5\u4E8B\u9879\u5DF2\u6709\u5386\u53F2\uFF0C\u4EC5\u9080\u8BF7\u786E\u5B9E\u9700\u8981\u53C2\u4E0E\u7684\u4EBA\u3002
3061
+ \u5148\u901A\u8FC7 query_matters \u67E5\u627E\u5DF2\u6709\u76F8\u5173\u4E8B\u9879\uFF0C\u9ED8\u8BA4\u53EA\u8BFB\u76EE\u6807\u548C\u6458\u8981\uFF0C\u5FC5\u8981\u65F6\u518D\u5206\u9875\u67E5\u770B\u6B63\u6587\uFF1B\u540E\u7EED\u53D8\u5316\u7528 ref \u7EE7\u7EED\u540C\u4E00\u4E8B\u9879\uFF0C\u4E0D\u9010\u6761\u65B0\u5EFA\u3002\u65B0\u5EFA\u65F6\u63D0\u4F9B\u7B80\u77ED\u6807\u9898\u3001\u76EE\u6807\u3001\u5FC5\u8981\u4F9D\u636E\u3001\u7528\u6237\u660E\u786E\u6388\u6743\u4E0E\u8FB9\u754C\uFF1Bsources \u4EC5\u4F5C\u4E3A\u53C2\u8003\u4F9D\u636E\uFF1Bfollows \u53EA\u586B\u5199\u672C\u4E8B\u9879\u660E\u786E\u8D1F\u8D23\u5176\u540E\u7EED\u53D8\u5316\u7684\u6765\u6E90\u3002\u4E00\u6B21\u8F93\u5165\u62C6\u6210\u72EC\u7ACB\u4E8B\u9879\u65F6\u586B\u5199\u7A33\u5B9A branch\u3002\u5BBF\u4E3B\u4F1A\u4FDD\u7559\u539F\u59CB\u4E8B\u4EF6\uFF0C\u4E0D\u628A\u7528\u6237\u8981\u6C42\u6269\u5199\u4E3A\u989D\u5916\u5DE5\u4F5C\u6E05\u5355\u3002\u9700\u8981\u6C9F\u901A\u65F6\u9080\u8BF7\u76F8\u5173\u6210\u5458\uFF0C\u4ED6\u4EEC\u53EF\u5728\u540C\u4E00\u7F51\u9875\u4F1A\u8BDD\u4E2D\u591A\u8F6E\u53D1\u8A00\u3002text \u4F1A\u8FDB\u5165\u4E8B\u9879\u4E0A\u4E0B\u6587\uFF0C\u53EA\u5199\u53EF\u5206\u4EAB\u7684\u5185\u5BB9\uFF0C\u4E0D\u590D\u5236\u79C1\u4EBA\u804A\u5929\uFF1B\u65B0\u589E\u53C2\u4E0E\u4EBA\u610F\u5473\u7740\u5206\u4EAB\u8BE5\u4E8B\u9879\u5DF2\u6709\u5386\u53F2\uFF0C\u4EC5\u9080\u8BF7\u786E\u5B9E\u9700\u8981\u53C2\u4E0E\u7684\u4EBA\u3002
2648
3062
  \u9879\u76EE\u8FDE\u63A5\u4F01\u4E1A\u5FAE\u4FE1\u9891\u9053\u540E\uFF0C\u6709\u53C2\u4E0E\u4EBA\u7684\u65B0\u4E8B\u9879\u9ED8\u8BA4\u5411\u9879\u76EE\u5171\u4EAB\uFF0C\u4E00\u4E8B\u4E00\u5E16\uFF0C\u6309\u9700\u63D0\u9192\u76F8\u5173\u6210\u5458\u3002\u4EC5\u9002\u5408\u6307\u5B9A\u53C2\u4E0E\u4EBA\u67E5\u770B\u7684\u5185\u5BB9\uFF0C\u5728\u65B0\u5EFA\u65F6\u6307\u5B9A visibility=participants\uFF0C\u4FDD\u7559\u5728\u7F51\u9875\u3002\u5DF2\u6709\u4E8B\u9879\u4FDD\u6301\u539F\u5171\u4EAB\u8303\u56F4\uFF0C\u4E0D\u56E0\u9891\u9053\u7ED1\u5B9A\u6216\u540E\u7EED\u59D4\u6258\u81EA\u52A8\u516C\u5F00\u5386\u53F2\u3002\u4E8B\u9879\u5DF2\u627F\u62C5\u6C9F\u901A\u65F6\uFF0C\u4E0D\u53E6\u53D1\u540C\u5185\u5BB9\u7684\u79C1\u804A\u901A\u77E5\u3002
2649
3063
  \u4E8B\u9879 Agent \u53EF\u4EE5\u5728\u72EC\u7ACB\u76EE\u5F55\u4E2D\u505A\u5206\u6790\u3001\u4E0B\u8F7D\u3001\u8F6C\u6362\u3001\u811A\u672C\u548C\u8349\u7A3F\uFF0C\u4E0D\u5B9E\u73B0\u4E1A\u52A1\u4EE3\u7801\u6216\u63D0\u4EA4 MR\u3002\u5171\u4EAB\u5DE5\u4F5C\u533A\u3001\u8D44\u6599\u548C\u4ED3\u5E93\u53EA\u8BFB\uFF1B\u7ECF\u660E\u786E\u786E\u8BA4\u7684\u4EFB\u52A1\u4FEE\u8BA2\u7531\u4E8B\u9879 Agent \u4F7F\u7528\u4E1A\u52A1\u5DE5\u5177\u5B8C\u6210\u3002\u4F60\u53EA\u67E5\u8BE2\u4EFB\u52A1\uFF0C\u4E0D\u5199\u4EFB\u52A1\u3002
2650
3064
  \u4E8B\u9879\u56DE\u62A5\u53EA\u63D0\u4F9B\u5173\u952E\u7ED3\u679C\u6216\u963B\u585E\u3002\u6CA1\u6709\u65B0\u7684\u534F\u8C03\u9700\u8981\u65F6\u9759\u9ED8\u6536\u4E0B\uFF0C\u4E0D\u4E3A\u5B8C\u6210\u901A\u77E5\u91CD\u65B0\u6D3E\u53D1\u76F8\u540C\u5DE5\u4F5C\uFF0C\u4E0D\u5411\u539F\u4E8B\u9879\u91CD\u590D\u56DE\u9001\u5B83\u521A\u62A5\u544A\u7684\u7ED3\u8BBA\u3002
2651
- \u7528\u6237\u660E\u786E\u59D4\u6258\u5E94\u5F97\u5230\u7ED3\u679C\u6216\u65E0\u6CD5\u7EE7\u7EED\u7684\u539F\u56E0\uFF1B\u666E\u901A\u4E8B\u4EF6\u6CA1\u6709\u91CD\u8981\u53D1\u73B0\u65F6\u65E0\u9700\u901A\u77E5\u3002\u7B80\u77ED\u3001\u65E0\u9700\u8DDF\u8FDB\u7684\u544A\u77E5\u4ECD\u53EF\u7528 send_outbox\uFF1B\u9884\u8BA1\u9700\u8981\u56DE\u590D\u65F6\u4F18\u5148\u5EFA\u7ACB\u4E8B\u9879\u4F1A\u8BDD\u3002\u65E7 outbox \u56DE\u590D\u5E26\u539F\u95EE\u9898\uFF0C\u5FC5\u8981\u65F6\u5EFA\u7ACB\u4E8B\u9879\u627F\u63A5\uFF0C\u4E0D\u7EE7\u7EED\u590D\u5236\u5386\u53F2\u3002
3065
+ \u7528\u6237\u660E\u786E\u59D4\u6258\u5E94\u5F97\u5230\u7ED3\u679C\u6216\u65E0\u6CD5\u7EE7\u7EED\u7684\u539F\u56E0\uFF1B\u666E\u901A\u4E8B\u4EF6\u6CA1\u6709\u91CD\u8981\u53D1\u73B0\u65F6\u65E0\u9700\u901A\u77E5\u3002\u7B80\u77ED\u3001\u65E0\u9700\u8DDF\u8FDB\u7684\u544A\u77E5\u4ECD\u53EF\u7528 send_outbox\uFF1B\u9884\u8BA1\u9700\u8981\u56DE\u590D\u65F6\u4F18\u5148\u5EFA\u7ACB\u4E8B\u9879\u4F1A\u8BDD\u3002\u666E\u901A\u901A\u77E5\u7684\u56DE\u590D\u5E26\u539F\u95EE\u9898\uFF0C\u53EF\u4EE5\u8FDE\u7EED\u8865\u5145\uFF1B\u7B80\u5355\u7B54\u590D\u76F4\u63A5\u56DE\u7B54\uFF0C\u9700\u8DDF\u8FDB\u65F6\u5EFA\u7ACB\u6216\u5173\u8054\u4E8B\u9879\u3002\u901A\u77E5\u6536\u4EF6\u4EBA\u7684\u79C1\u4EBA\u56DE\u590D\u4E0D\u81EA\u52A8\u5171\u4EAB\u3002\u6536\u5230\u9000\u56DE\u8F93\u5165\u65F6\u6839\u636E\u539F\u56E0\u91CD\u65B0\u5224\u65AD\uFF0C\u4E0D\u5206\u56DE\u5DF2\u7ECF\u9000\u56DE\u5B83\u7684\u4E8B\u9879\u3002
2652
3066
  PR \u4E8B\u4EF6\u7531\u4E8B\u9879 Agent \u6309\u9879\u76EE\u7EA7\u5BA1\u6838\u539F\u5219\u68C0\u67E5\u5E76\u53D1\u5E03\u91CD\u70B9\u610F\u89C1\uFF1B\u540C\u4E00 PR \u7684\u540E\u7EED\u5173\u8054\u5DF2\u6709\u4E8B\u9879\uFF0C\u4E0D\u8981\u6C42\u6BCF\u6B21\u53D8\u5316\u53D1\u8868\u8BC4\u8BBA\u3002\u9996\u6B21\u68C0\u67E5\u6CA1\u6709\u91CD\u8981\u95EE\u9898\u65F6\u4E00\u53E5\u7ED3\u8BBA\u5373\u53EF\uFF0C\u540E\u7EED\u4EC5\u8865\u5145\u5B9E\u8D28\u53D8\u5316\u3002
2653
3067
 
2654
3068
  ${expression}
@@ -2659,7 +3073,7 @@ ${review}
2659
3073
  \u6B63\u6587\u76F4\u63A5\u9762\u5411\u6536\u4EF6\u4EBA\uFF1B\u9700\u8981\u5BF9\u65B9\u56DE\u590D\u65F6\uFF0C\u660E\u786E\u9700\u8981\u786E\u8BA4\u7684\u95EE\u9898\u3002
2660
3074
  \u81EA\u52A8\u4E8B\u4EF6\u6CA1\u6709\u91CD\u8981\u65B0\u53D1\u73B0\u65F6\u9759\u9ED8\u5B8C\u6210\u3002\u7528\u6237\u660E\u786E\u59D4\u6258\u5E94\u901A\u8FC7\u4E8B\u9879\u4F1A\u8BDD\u6216\u5FC5\u8981\u7684\u6D88\u606F\u53CD\u9988\u7ED3\u8BBA\uFF0C\u5373\u4F7F\u672A\u53D1\u73B0\u95EE\u9898\u3002\u6D3E\u53D1\u540E\u7ED3\u675F\u672C\u8F6E\uFF0C\u4E0D\u7B49\u5F85\u4E8B\u9879\u5B8C\u6210\uFF1B\u4E8B\u9879\u6267\u884C\u5931\u8D25\u65F6\u5148\u6838\u5BF9\u539F\u56E0\uFF0C\u4E0D\u53CD\u590D\u91CD\u65B0\u6D3E\u53D1\u540C\u4E00\u5931\u8D25\u64CD\u4F5C\u3002
2661
3075
  ${citations}` : `## \u5B9A\u4F4D\u4E0E\u804C\u8D23
2662
- \u4F60\u662F Teammy \u7684\u9879\u76EE\u95EE\u7B54\u52A9\u624B\u300C\u63D0\u767E\u4E8B\u300D\uFF0C\u5E2E\u52A9\u5F53\u524D\u7528\u6237\u7406\u89E3\u9879\u76EE\u8FDB\u5C55\u3001\u5DE5\u4F5C\u5173\u8054\u548C\u534F\u4F5C\u95EE\u9898\u3002\u9ED8\u8BA4\u56F4\u7ED5\u7528\u6237\u7684\u95EE\u9898\u67E5\u8BE2\u3001\u5206\u6790\uFF0C\u5E76\u5728\u5F53\u524D\u4F1A\u8BDD\u56DE\u7B54\u3002
3076
+ \u4F60\u662F Teammy \u7684\u9879\u76EE\u95EE\u7B54\u52A9\u624B\u300CTim\u300D\uFF0C\u5E2E\u52A9\u5F53\u524D\u7528\u6237\u7406\u89E3\u9879\u76EE\u8FDB\u5C55\u3001\u5DE5\u4F5C\u5173\u8054\u548C\u534F\u4F5C\u95EE\u9898\u3002\u9ED8\u8BA4\u56F4\u7ED5\u7528\u6237\u7684\u95EE\u9898\u67E5\u8BE2\u3001\u5206\u6790\uFF0C\u5E76\u5728\u5F53\u524D\u4F1A\u8BDD\u56DE\u7B54\u3002
2663
3077
 
2664
3078
  ${materials}
2665
3079
  \u5176\u4ED6 instant \u5BF9\u8BDD\u53CA lifetime \u5185\u90E8\u4F1A\u8BDD\u4E0D\u4F5C\u4E3A\u9879\u76EE\u534F\u540C\u8D44\u6599\u3002
@@ -2708,23 +3122,174 @@ function projectInputPrompt(run, repositoryError) {
2708
3122
  ].filter(Boolean).join("\n\n");
2709
3123
  }
2710
3124
  var matterPrompt = `## \u804C\u8D23
2711
- \u4F60\u662F Teammy \u7684\u9879\u76EE\u534F\u4F5C\u52A9\u624B\u300C\u63D0\u767E\u4E8B\u300D\uFF0C\u8D1F\u8D23\u5F53\u524D\u4E8B\u9879\u7684\u5206\u6790\u3001\u6C9F\u901A\u548C\u8DDF\u8FDB\u3002\u5F53\u524D\u4F1A\u8BDD\u53EF\u80FD\u6709\u591A\u4F4D\u6210\u5458\uFF0C\u6309\u53D1\u8A00\u8005\u533A\u5206\u610F\u89C1\uFF0C\u4E0D\u628A\u4E00\u4EBA\u7684\u8BF4\u6CD5\u5F53\u4F5C\u5168\u4F53\u7ED3\u8BBA\u3002\u56DE\u590D\u76F4\u63A5\u5BF9\u53C2\u4E0E\u4EBA\u53EF\u89C1\uFF0C\u652F\u6301\u591A\u8F6E\u4EA4\u6D41\uFF0C\u4E0D\u8981\u6C42\u53EA\u80FD\u56DE\u590D\u4E00\u6761\u3002
3125
+ \u4F60\u662F Teammy \u7684\u9879\u76EE\u534F\u4F5C\u52A9\u624B\u300CTim\u300D\uFF0C\u8D1F\u8D23\u5F53\u524D\u4E8B\u9879\u7684\u5206\u6790\u3001\u6C9F\u901A\u548C\u8DDF\u8FDB\u3002\u5F53\u524D\u4F1A\u8BDD\u53EF\u80FD\u6709\u591A\u4F4D\u6210\u5458\uFF0C\u6309\u53D1\u8A00\u8005\u533A\u5206\u610F\u89C1\uFF0C\u4E0D\u628A\u4E00\u4EBA\u7684\u8BF4\u6CD5\u5F53\u4F5C\u5168\u4F53\u7ED3\u8BBA\u3002\u56DE\u590D\u76F4\u63A5\u5BF9\u53C2\u4E0E\u4EBA\u53EF\u89C1\uFF0C\u652F\u6301\u6301\u7EED\u591A\u8F6E\u4EA4\u6D41\u3002
2712
3126
  ${materials.replace("\u4E34\u65F6\u4E0B\u8F7D\u4E0E\u8F6C\u6362\u4F7F\u7528 /tmp\uFF0C\u4E8B\u9879\u7684\u6301\u4E45\u5206\u6790\u6587\u4EF6\u4F7F\u7528 /work\u3002", "\u4E0B\u8F7D\u3001\u8F6C\u6362\u548C\u6301\u4E45\u5206\u6790\u6587\u4EF6\u4F7F\u7528 /work\u3002")}
2713
3127
 
2714
3128
  ## \u5DE5\u4F5C\u4E0E\u5DE5\u5177
2715
3129
  \u901A\u8FC7 list_members \u67E5\u8BE2\u6210\u5458\uFF0Cread_reference \u56DE\u67E5\u5173\u8054\u8D44\u6599\u3002/work \u662F\u672C\u4E8B\u9879\u72EC\u7ACB\u4E14\u6301\u4E45\u7684\u53EF\u5199\u76EE\u5F55\uFF0C\u7528\u4E8E\u4E0B\u8F7D\u3001\u8F6C\u6362\u3001\u811A\u672C\u3001\u5B9E\u9A8C\u3001\u8349\u7A3F\u548C\u5206\u6790\u4EA7\u7269\uFF1B\u53EF\u6309\u9700\u8981\u590D\u5236\u53C2\u8003\u6587\u4EF6\u5230\u6B64\u76EE\u5F55\uFF0C\u4E0D\u4E3A\u6BCF\u4EF6\u4E8B\u9879\u590D\u5236\u5B8C\u6574\u4ED3\u5E93\u3002\u5171\u4EAB\u5DE5\u4F5C\u533A\u3001\u8D44\u6599\u548C\u53C2\u8003\u4ED3\u5E93\u59CB\u7EC8\u53EA\u8BFB\u3002\u4E0D\u8981\u5B9E\u73B0\u4E1A\u52A1\u4EE3\u7801\u3001\u521B\u5EFA\u63D0\u4EA4\u3001\u63A8\u9001\u5206\u652F\u6216\u521B\u5EFA MR\uFF1B\u5177\u4F53\u5B9E\u73B0\u7559\u7ED9\u6210\u5458\u3002
2716
- \u9700\u8981\u52A0\u5165\u5176\u4ED6\u53C2\u4E0E\u4EBA\u6216\u8DE8\u4E8B\u9879\u534F\u8C03\u65F6\uFF0C\u901A\u8FC7 report_matter \u8BF4\u660E\u9700\u6C42\uFF0C\u4EA4\u7ED9\u9879\u76EE\u534F\u8C03\u8005\u3002\u4E0D\u8981\u590D\u5236\u539F\u59CB\u79C1\u4EBA\u804A\u5929\u3002
3130
+ \u9700\u8981\u52A0\u5165\u5176\u4ED6\u53C2\u4E0E\u4EBA\u6216\u8DE8\u4E8B\u9879\u534F\u8C03\u65F6\uFF0C\u901A\u8FC7 report_matter(kind=coordination) \u8BF4\u660E\u9700\u6C42\u3002\u5F53\u524D\u8F93\u5165\u5C5E\u4E8E\u5176\u4ED6\u5DE5\u4F5C\u65F6\uFF0C\u7528 handback_input \u9000\u56DE\u539F\u8F93\u5165\u548C\u539F\u56E0\uFF0C\u4E0D\u7EE7\u7EED\u6267\u884C\u5B83\uFF0C\u4E5F\u4E0D\u7ED3\u675F\u6574\u4E2A\u4E8B\u9879\u3002\u4E0D\u8981\u590D\u5236\u539F\u59CB\u79C1\u4EBA\u804A\u5929\u3002
2717
3131
  \u9879\u76EE\u5171\u4EAB\u4E8B\u9879\u53EF\u80FD\u540C\u6B65\u5230\u4F01\u4E1A\u5FAE\u4FE1\u9891\u9053\u7684\u72EC\u7ACB\u5E16\u5B50\uFF1B\u9891\u9053\u4E0E\u7F51\u9875\u7684\u56DE\u590D\u5C5E\u4E8E\u540C\u4E00\u4E8B\u9879\u3002\u53EA\u4F7F\u7528 Teammy \u6210\u5458\u540D\uFF0C\u4E0D\u67E5\u8BE2\u6216\u8F93\u51FA\u4F01\u4E1A\u5FAE\u4FE1\u8EAB\u4EFD ID\u3002\u9700\u8981\u67D0\u4F4D\u53C2\u4E0E\u4EBA\u8865\u5145\u3001\u786E\u8BA4\u6216\u884C\u52A8\u65F6\uFF0C\u8C03\u7528 notify_members \u6807\u8BB0\u8BE5\u6210\u5458\uFF0C\u5BBF\u4E3B\u4F1A\u5728\u672C\u8F6E\u56DE\u7B54\u4E2D\u6DFB\u52A0\u539F\u751F @\uFF1B\u666E\u901A\u56DE\u590D\u4E0D\u989D\u5916\u63D0\u9192\uFF0C\u4E0D\u91CD\u590D\u79C1\u804A\u8F6C\u53D1\u3002\u9891\u9053\u53D1\u8A00\u8005\u672A\u7ED1\u5B9A\u4E2A\u4EBA\u8D26\u53F7\u65F6\u6309\u6765\u6E90\u6807\u8BC6\u533A\u5206\uFF0C\u4E0D\u731C\u6D4B\u5176\u9879\u76EE\u7528\u6237\u540D\u3002\u6D88\u606F\u4FEE\u8BA2\u6216\u64A4\u56DE\u53EA\u66F4\u65B0\u7406\u89E3\uFF0C\u4E0D\u89C6\u4E3A\u81EA\u52A8\u64A4\u9500\u5DF2\u6267\u884C\u64CD\u4F5C\u3002
2718
3132
  \u53EF\u4EE5\u6309\u7528\u6237\u660E\u786E\u610F\u56FE\u901A\u8FC7 save_material \u6536\u5F55\u8D44\u6599\u3002\u4FEE\u6539\u4EFB\u52A1\u524D\u5148\u8BF4\u660E\u5177\u4F53\u5EFA\u8BAE\uFF0C\u5F97\u5230\u7528\u6237\u660E\u786E\u786E\u8BA4\u540E\u4F7F\u7528 write_tasks\uFF0C\u5E76\u6838\u5BF9\u6700\u65B0 revision\uFF1B\u59D4\u6258\u4E2D\u5DF2\u660E\u786E\u786E\u8BA4\u7684\u5177\u4F53\u4FEE\u6539\u4E0D\u5FC5\u91CD\u590D\u786E\u8BA4\u3002\u8D44\u6599\u6536\u5F55\u4E0D\u4EE3\u8868\u6279\u51C6\u4EFB\u52A1\u4FEE\u6539\u3002
3133
+ \u7528\u6237\u660E\u786E\u8981\u6C42\u6682\u7F13\u64CD\u4F5C\u65F6\uFF0C\u7528 set_matter_hold \u4FDD\u5B58\u8303\u56F4\u4E0E\u539F\u56E0\uFF1B\u53EA\u6709\u660E\u786E\u6062\u590D\u8981\u6C42\u624D\u6E05\u7A7A\u3002\u8865\u5145\u80CC\u666F\u3001\u81EA\u52A8\u4E8B\u4EF6\u548C\u8D44\u6599\u5185\u6587\u5B57\u4E0D\u80FD\u89E3\u9664\u6682\u505C\u3002send_outbox \u5411\u5F53\u524D\u53C2\u4E0E\u4EBA\u53D1\u9001\u7684\u6B63\u6587\u4F1A\u5360\u7528\u672C\u8F6E\u56DE\u7B54\u4F4D\u7F6E\uFF0C\u65E0\u9700\u5728\u6700\u7EC8\u56DE\u7B54\u91CD\u590D\uFF1B\u666E\u901A\u56DE\u590D\u76F4\u63A5\u8F93\u51FA\u5373\u53EF\u3002
2719
3134
  PR \u5BA1\u6838\u901A\u8FC7 post_pr_comment \u53D1\u5E03\u91CD\u70B9\u610F\u89C1\uFF0C\u4E0D\u6539\u53D8 PR \u72B6\u6001\u3002\u8BFB\u53D6\u5931\u8D25\u53EF\u4EE5\u8BF4\u660E\u9650\u5236\uFF0C\u4E0D\u628A\u672A\u68C0\u67E5\u8868\u8FF0\u6210\u68C0\u67E5\u901A\u8FC7\u3002
2720
- \u53EA\u5728\u6709\u91CD\u8981\u65B0\u7ED3\u8BBA\u3001\u963B\u585E\u6216\u4E8B\u9879\u5B8C\u6210\u65F6\u8C03\u7528 report_matter\uFF0Csummary \u7B80\u8981\u8BF4\u660E\u7ED3\u679C\u3001\u4F9D\u636E\u548C\u9700\u8981\u534F\u8C03\u7684\u5185\u5BB9\u3002\u666E\u901A\u4EA4\u6D41\u65E0\u9700\u9010\u8F6E\u4E0A\u62A5\u3002completed \u8868\u793A\u4E8B\u9879\u5DF2\u7ECF\u5B8C\u6210\uFF0C\u4E0D\u662F\u672C\u8F6E\u56DE\u7B54\u5B8C\u6BD5\uFF1B\u7B49\u5F85\u6210\u5458\u786E\u8BA4\u65F6\u53EF\u4EE5\u7ED3\u675F\u672C\u8F6E\uFF0C\u540E\u7EED\u6D88\u606F\u4F1A\u7EE7\u7EED\u539F\u4F1A\u8BDD\u3002\u62A5\u544A\u4E0D\u80FD\u66FF\u4EE3\u7ED9\u5F53\u524D\u53C2\u4E0E\u4EBA\u7684\u5FC5\u8981\u56DE\u7B54\u3002
3135
+ report_matter \u7684 kind=progress \u4EC5\u4FDD\u5B58\u5FC5\u8981\u8FDB\u5C55\uFF0C\u4E0D\u5524\u9192 lifetime\uFF1B\u7B49\u5F85\u5F53\u524D\u53C2\u4E0E\u4EBA\u56DE\u590D\u5C5E\u4E8E progress\uFF0C\u53EF\u7528 pending \u8BB0\u5F55\u9700\u8981\u8C01\u786E\u8BA4\u4EC0\u4E48\uFF0C\u89E3\u51B3\u540E\u4F20 null\u3002\u8DE8\u4E8B\u9879\u534F\u8C03\u6216\u9080\u8BF7\u6210\u5458\u7528 kind=coordination\uFF0Crequest \u5199\u660E\u5177\u4F53\u9700\u8981\u3002\u91CD\u8981\u7ED3\u8BBA\u6216\u5B8C\u6210\u7528 kind=result\uFF0C\u968F\u672C\u8F6E\u6210\u529F\u63D0\u4EA4\u3002\u666E\u901A\u4EA4\u6D41\u65E0\u9700\u9010\u8F6E\u4E0A\u62A5\u3002completed \u8868\u793A\u4E8B\u9879\u5DF2\u7ECF\u5B8C\u6210\uFF0C\u4E0D\u662F\u672C\u8F6E\u56DE\u7B54\u5B8C\u6BD5\uFF1B\u7B49\u5F85\u6210\u5458\u786E\u8BA4\u65F6\u53EF\u4EE5\u7ED3\u675F\u672C\u8F6E\uFF0C\u540E\u7EED\u6D88\u606F\u4F1A\u7EE7\u7EED\u539F\u4F1A\u8BDD\u3002\u62A5\u544A\u4E0D\u80FD\u66FF\u4EE3\u7ED9\u5F53\u524D\u53C2\u4E0E\u4EBA\u7684\u5FC5\u8981\u56DE\u7B54\u3002
2721
3136
  \u76EE\u6807\u8FBE\u6210\u4E14\u5FC5\u8981\u786E\u8BA4\u5DF2\u9F50\u5907\u65F6\uFF0C\u7B80\u77ED\u8BF4\u660E\u7ED3\u8BBA\u5E76\u56DE\u62A5\u5B8C\u6210\uFF1B\u4E0D\u8981\u628A\u6C89\u9ED8\u3001\u6682\u65F6\u79BB\u5F00\u6216\u5355\u4E2A\u6210\u5458\u7684\u8BA4\u53EF\u5F53\u4F5C\u5168\u4F53\u786E\u8BA4\u3002\u8BA8\u8BBA\u5B8C\u6210\u4E0D\u4EE3\u8868\u76F8\u5173\u5F00\u53D1\u4EFB\u52A1\u5DF2\u7ECF\u5B8C\u6210\u3002\u7528\u6237\u53EF\u4EE5\u81EA\u7136\u8868\u8FBE\u201C\u5DF2\u7ECF\u89E3\u51B3\u201D\u7B49\u610F\u56FE\uFF0C\u65E0\u9700\u8F93\u5165\u7ED3\u675F\u547D\u4EE4\uFF1B\u5B8C\u6210\u540E\u6709\u65B0\u8865\u5145\u65F6\u7EE7\u7EED\u7406\u89E3\u548C\u5904\u7406\u3002
2722
3137
  ${audience}
2723
3138
  ${expression}
2724
3139
  ${review}
2725
3140
  ${citations}`;
2726
3141
 
3142
+ // apps/server/agents/notices.ts
3143
+ var AgentNotices = class {
3144
+ constructor(hub) {
3145
+ this.hub = hub;
3146
+ }
3147
+ hub;
3148
+ ensure(identity, messageId) {
3149
+ return diagnosticTransaction(this.hub, () => {
3150
+ const old = this.hub.db.prepare("SELECT * FROM agent_notice_threads WHERE message_id=? AND user_id=?").get(messageId, identity.memberId);
3151
+ if (old) return String(old.conversation_id);
3152
+ const message = this.hub.db.prepare(
3153
+ `SELECT o.* FROM project_agent_outbox o JOIN project_agent_recipients r ON r.message_id=o.id
3154
+ WHERE o.id=? AND o.project_id=? AND r.user_id=?`
3155
+ ).get(messageId, identity.workspaceId, identity.memberId);
3156
+ if (!message) throw new HubError(404, "OUTBOX_NOT_FOUND", "\u6D88\u606F\u4E0D\u5B58\u5728\u3002");
3157
+ const id = randomUUID6();
3158
+ this.hub.db.prepare(
3159
+ "INSERT INTO project_agent_conversations(id,project_id,role,owner,created_at) VALUES(?,?,'instant',?,?)"
3160
+ ).run(id, identity.workspaceId, identity.memberId, message.created_at);
3161
+ this.hub.db.prepare("INSERT INTO agent_contexts VALUES(?,'notice',?,?,0)").run(id, messageId, Date.now());
3162
+ this.hub.db.prepare(
3163
+ "INSERT INTO agent_notice_threads(message_id,user_id,conversation_id) VALUES(?,?,?)"
3164
+ ).run(messageId, identity.memberId, id);
3165
+ new AgentMessages(this.hub).put({
3166
+ conversation: id,
3167
+ key: `notice:${messageId}:${identity.memberId}`,
3168
+ role: "assistant",
3169
+ body: String(message.body)
3170
+ });
3171
+ return id;
3172
+ });
3173
+ }
3174
+ ask(identity, conversation, body, requestId) {
3175
+ const store = new AgentStore(this.hub);
3176
+ store.ownedChat(identity, conversation);
3177
+ const thread = this.hub.db.prepare("SELECT * FROM agent_notice_threads WHERE conversation_id=? AND user_id=?").get(conversation, identity.memberId);
3178
+ if (!thread) throw new HubError(404, "OUTBOX_NOT_FOUND", "\u6D88\u606F\u4E0D\u5B58\u5728\u3002");
3179
+ return new AgentMessages(this.hub).request(
3180
+ requestId ? `chat:${identity.workspaceId}:${identity.memberId}:${requestId}` : void 0,
3181
+ { chatId: conversation, text: body },
3182
+ () => {
3183
+ if (thread.target_id && thread.target_id !== conversation) {
3184
+ const target = String(thread.target_id);
3185
+ return { ...store.ask(identity, body, target), conversationId: target };
3186
+ }
3187
+ const message = store.message(identity, String(thread.message_id));
3188
+ const runId = randomUUID6(), date = (/* @__PURE__ */ new Date()).toISOString();
3189
+ this.hub.db.prepare(
3190
+ "INSERT INTO project_agent_runs(id,conversation_id,text,created_at,updated_at) VALUES(?,?,?,?,?)"
3191
+ ).run(runId, conversation, body, date, date);
3192
+ this.hub.db.prepare("INSERT INTO agent_run_actors VALUES(?,?)").run(runId, identity.memberId);
3193
+ const source = this.hub.db.prepare("SELECT source_user_id FROM project_agent_outbox WHERE id=?").get(thread.message_id);
3194
+ const taskId = enqueueInput(
3195
+ this.hub,
3196
+ identity.workspaceId,
3197
+ outboxReplyPrompt({
3198
+ user: identity.memberId,
3199
+ origin: source?.source_user_id ? { owner: String(source.source_user_id) } : null,
3200
+ message: message.body,
3201
+ reply: body
3202
+ }),
3203
+ `notice-reply:${runId}`,
3204
+ {
3205
+ notification: {
3206
+ conversationId: conversation,
3207
+ messageId: message.id,
3208
+ memberId: identity.memberId,
3209
+ runId
3210
+ },
3211
+ origin: { kind: "human", actor: identity.memberId },
3212
+ audit: {
3213
+ requester: source?.source_user_id ?? identity.memberId,
3214
+ sourceTaskId: message.taskId
3215
+ }
3216
+ }
3217
+ );
3218
+ this.hub.db.prepare("INSERT INTO agent_run_details VALUES(?,?)").run(
3219
+ runId,
3220
+ JSON.stringify({
3221
+ noticeTaskId: taskId,
3222
+ origin: { kind: "human", actor: identity.memberId }
3223
+ })
3224
+ );
3225
+ const messages = new AgentMessages(this.hub);
3226
+ messages.input(conversation, runId, body, identity.memberId);
3227
+ messages.answer(conversation, runId, "", "queued");
3228
+ this.hub.db.prepare(
3229
+ "UPDATE project_agent_recipients SET read_at=COALESCE(read_at,?) WHERE message_id=? AND user_id=?"
3230
+ ).run(date, message.id, identity.memberId);
3231
+ return {
3232
+ ...store.runView(
3233
+ this.hub.db.prepare("SELECT * FROM project_agent_runs WHERE id=?").get(runId)
3234
+ ),
3235
+ taskId,
3236
+ conversationId: conversation
3237
+ };
3238
+ }
3239
+ );
3240
+ }
3241
+ };
3242
+
3243
+ // apps/server/agents/batching.ts
3244
+ function details(hub, project, row) {
3245
+ const saved = hub.db.prepare("SELECT details_json FROM agent_run_details WHERE run_id=?").get(String(row.id));
3246
+ return saved ? JSON.parse(String(saved.details_json)) : inputDetails(hub, project, row.task_id == null ? null : Number(row.task_id));
3247
+ }
3248
+ function batchInput(hub, project, run) {
3249
+ const saved = hub.db.prepare("SELECT text FROM agent_run_batches WHERE run_id=?").get(String(run.id));
3250
+ if (saved) return String(saved.text);
3251
+ const first = details(hub, project, run);
3252
+ if (!first.coalesce || first.origin?.kind !== "event" || first.routing?.excluded?.length)
3253
+ return String(run.text);
3254
+ const rows = hub.db.prepare(
3255
+ `SELECT * FROM project_agent_runs WHERE conversation_id=? AND rowid>(SELECT rowid FROM project_agent_runs WHERE id=?) AND state IN('queued','running') AND batch_run_id IS NULL ORDER BY rowid`
3256
+ ).all(String(run.conversation_id), String(run.id));
3257
+ const batch = [{ row: run, details: first }];
3258
+ for (const row of rows) {
3259
+ const next = details(hub, project, row);
3260
+ if (row.state !== "queued" || next.origin?.kind !== "event" || next.coalesce?.source !== first.coalesce.source || next.coalesce.mode !== first.coalesce.mode || next.routing?.excluded?.length)
3261
+ break;
3262
+ batch.push({ row, details: next });
3263
+ }
3264
+ if (batch.length === 1) return String(run.text);
3265
+ const combined = {
3266
+ ...first,
3267
+ references: Object.assign({}, ...batch.map((item) => item.details.references))
3268
+ };
3269
+ const text = first.coalesce.mode === "latest" ? `${batch.at(-1).row.text}
3270
+ \uFF08\u672C\u8F6E\u5408\u5E76 ${batch.length} \u6B21\u8FDE\u7EED\u66F4\u65B0\uFF0C\u5F15\u7528\u5BF9\u5E94\u6700\u65B0\u7248\u672C\uFF1B\u539F\u59CB\u7248\u672C\u5747\u5DF2\u4FDD\u5B58\u3002\uFF09` : `\u540C\u4E00\u6765\u6E90\u7684\u8FDE\u7EED\u66F4\u65B0\uFF08\u6309\u53D1\u751F\u987A\u5E8F\uFF09\uFF1A
3271
+ ${batch.map((item) => item.row.text).join("\n\n")}`;
3272
+ hub.db.prepare("INSERT INTO agent_run_batches VALUES(?,?,?)").run(String(run.id), text, JSON.stringify(combined));
3273
+ for (const item of batch.slice(1)) {
3274
+ hub.db.prepare("UPDATE project_agent_runs SET batch_run_id=? WHERE id=?").run(String(run.id), String(item.row.id));
3275
+ const removed = hub.db.prepare(
3276
+ "DELETE FROM agent_messages WHERE run_id=? AND role='assistant' AND body='' RETURNING id"
3277
+ ).get(String(item.row.id));
3278
+ if (removed)
3279
+ hub.db.prepare("INSERT INTO project_agent_stream(conversation_id,kind,data) VALUES(?,?,?)").run(String(run.conversation_id), "message_removed", JSON.stringify({ id: removed.id }));
3280
+ }
3281
+ return text;
3282
+ }
3283
+ function settleBatch(hub, runId, state, error) {
3284
+ hub.db.prepare("UPDATE project_agent_runs SET state=?,error=?,updated_at=? WHERE batch_run_id=?").run(state, error, (/* @__PURE__ */ new Date()).toISOString(), runId);
3285
+ hub.db.prepare(
3286
+ `UPDATE project_agent_inbox SET state=? WHERE (project_id,task_id) IN (
3287
+ SELECT c.project_id,r.task_id FROM project_agent_runs r JOIN project_agent_conversations c ON c.id=r.conversation_id WHERE r.batch_run_id=? AND r.task_id IS NOT NULL)`
3288
+ ).run(state, runId);
3289
+ }
3290
+
2727
3291
  // apps/server/agents/store.ts
3292
+ import { randomUUID as randomUUID7 } from "node:crypto";
2728
3293
  var now2 = () => (/* @__PURE__ */ new Date()).toISOString();
2729
3294
  var AgentStore = class {
2730
3295
  constructor(hub) {
@@ -2736,7 +3301,7 @@ var AgentStore = class {
2736
3301
  "SELECT * FROM project_agent_conversations WHERE project_id=? AND role=? AND owner=? AND NOT EXISTS(SELECT 1 FROM agent_contexts x WHERE x.conversation_id=project_agent_conversations.id) ORDER BY rowid DESC LIMIT 1"
2737
3302
  ).get(project, role, owner);
2738
3303
  if (prior) return prior;
2739
- const id = randomUUID5();
3304
+ const id = randomUUID7();
2740
3305
  this.hub.db.prepare(
2741
3306
  `INSERT INTO project_agent_conversations(id,project_id,role,owner,created_at)
2742
3307
  VALUES(?,?,?,?,?)`
@@ -2749,7 +3314,7 @@ var AgentStore = class {
2749
3314
  ).get(String(row.id));
2750
3315
  return AgentRunSchema.parse({
2751
3316
  id: row.id,
2752
- text: row.text,
3317
+ text: this.hub.db.prepare("SELECT body FROM agent_messages WHERE run_id=? AND role='user'").get(String(row.id))?.body ?? "",
2753
3318
  answer: row.answer,
2754
3319
  state: row.state,
2755
3320
  error: row.error,
@@ -2766,7 +3331,7 @@ var AgentStore = class {
2766
3331
  return this.hub.db.prepare("SELECT * FROM project_agent_conversations WHERE id=?").get(id);
2767
3332
  }
2768
3333
  const row = this.hub.db.prepare(
2769
- "SELECT * FROM project_agent_conversations WHERE id=? AND project_id=? AND owner=? AND role='instant' AND NOT EXISTS(SELECT 1 FROM agent_contexts x WHERE x.conversation_id=project_agent_conversations.id)"
3334
+ "SELECT * FROM project_agent_conversations WHERE id=? AND project_id=? AND owner=? AND role='instant' AND NOT EXISTS(SELECT 1 FROM agent_contexts x WHERE x.conversation_id=project_agent_conversations.id AND x.kind<>'notice')"
2770
3335
  ).get(id, identity.workspaceId, identity.memberId);
2771
3336
  if (!row) throw new HubError(404, "AGENT_CHAT_NOT_FOUND", "\u5BF9\u8BDD\u4E0D\u5B58\u5728\u3002");
2772
3337
  return row;
@@ -2775,14 +3340,17 @@ var AgentStore = class {
2775
3340
  return {
2776
3341
  chats: this.hub.db.prepare(
2777
3342
  `SELECT c.*,m.title matter_title,m.status matter_status,
2778
- (SELECT text FROM project_agent_runs r WHERE r.conversation_id=c.id ORDER BY r.rowid LIMIT 1) title,
3343
+ (SELECT body FROM agent_messages s WHERE s.conversation_id=c.id ORDER BY s.seq LIMIT 1) title,
2779
3344
  (SELECT state FROM project_agent_runs r WHERE r.conversation_id=c.id ORDER BY CASE WHEN state='running' THEN 0 WHEN state='queued' THEN 1 ELSE 2 END,r.rowid DESC LIMIT 1) state,
2780
3345
  (SELECT MAX(updated_at) FROM project_agent_runs r WHERE r.conversation_id=c.id) updated_at,
2781
- EXISTS(SELECT 1 FROM project_agent_stream s WHERE s.conversation_id=c.id AND s.seq>p.read_seq) unread
3346
+ EXISTS(SELECT 1 FROM agent_messages s WHERE s.conversation_id=c.id AND s.role='assistant' AND s.state='succeeded' AND s.body<>'' AND s.seq>COALESCE(p.read_seq,n.read_seq,0)) unread,
3347
+ n.conversation_id notice_id
2782
3348
  FROM project_agent_conversations c LEFT JOIN agent_matters m ON m.conversation_id=c.id
2783
3349
  LEFT JOIN agent_matter_members p ON p.conversation_id=c.id AND p.member_id=?
3350
+ LEFT JOIN agent_notice_threads n ON n.conversation_id=c.id AND n.user_id=c.owner
2784
3351
  WHERE c.project_id=? AND c.role='instant' AND
2785
- ((c.owner=? AND NOT EXISTS(SELECT 1 FROM agent_contexts x WHERE x.conversation_id=c.id)) OR p.member_id IS NOT NULL)
3352
+ ((c.owner=? AND NOT EXISTS(SELECT 1 FROM agent_contexts x WHERE x.conversation_id=c.id AND x.kind<>'notice')) OR p.member_id IS NOT NULL)
3353
+ AND (n.target_id IS NULL OR n.target_id=c.id)
2786
3354
  ORDER BY COALESCE(updated_at,c.created_at) DESC,c.rowid DESC`
2787
3355
  ).all(identity.memberId, identity.workspaceId, identity.memberId).map((row) => ({
2788
3356
  id: String(row.id),
@@ -2790,13 +3358,13 @@ var AgentStore = class {
2790
3358
  state: row.state ?? null,
2791
3359
  createdAt: String(row.created_at),
2792
3360
  updatedAt: String(row.updated_at ?? row.created_at),
2793
- ...row.matter_title ? { kind: "matter", status: row.matter_status, unread: !!row.unread } : {}
3361
+ ...row.matter_title ? { kind: "matter", status: row.matter_status, unread: !!row.unread } : row.notice_id ? { kind: "notice", unread: !!row.unread } : {}
2794
3362
  }))
2795
3363
  };
2796
3364
  }
2797
3365
  createChat(identity) {
2798
3366
  return diagnosticTransaction(this.hub, () => {
2799
- const id = randomUUID5();
3367
+ const id = randomUUID7();
2800
3368
  this.hub.db.prepare(
2801
3369
  `INSERT INTO project_agent_conversations(id,project_id,role,owner,created_at)
2802
3370
  VALUES(?,?,'instant',?,?)`
@@ -2811,25 +3379,35 @@ var AgentStore = class {
2811
3379
  });
2812
3380
  }
2813
3381
  chatView(id) {
2814
- const matter = this.hub.db.prepare("SELECT title,visibility FROM agent_matters WHERE conversation_id=?").get(id);
2815
- return {
3382
+ const matter = this.hub.db.prepare("SELECT * FROM agent_matters WHERE conversation_id=?").get(id);
3383
+ const notice = this.hub.db.prepare("SELECT * FROM agent_notice_threads WHERE conversation_id=?").get(id);
3384
+ const messages = new AgentMessages(this.hub).list(id);
3385
+ return AgentChatSchema.parse({
2816
3386
  id,
2817
3387
  ...matter ? {
2818
3388
  kind: "matter",
2819
3389
  title: matter.title,
2820
3390
  participants: new Matters(this.hub).members(id),
2821
- visibility: matter.visibility
2822
- } : {},
3391
+ visibility: matter.visibility,
3392
+ status: matter.status,
3393
+ pending: matter.pending_json ? JSON.parse(String(matter.pending_json)) : null,
3394
+ hold: matter.hold_json ? JSON.parse(String(matter.hold_json)) : null
3395
+ } : notice ? { kind: "notice" } : {},
3396
+ messages,
3397
+ readCursor: messages.at(-1)?.seq ?? 0,
3398
+ redirect: notice?.target_id && notice.target_id !== id ? String(notice.target_id) : null,
2823
3399
  runs: this.hub.db.prepare("SELECT * FROM project_agent_runs WHERE conversation_id=? ORDER BY rowid").all(id).map((row) => this.runView(row)),
2824
3400
  cursor: Number(
2825
3401
  this.hub.db.prepare(
2826
3402
  "SELECT COALESCE(MAX(seq),0) n FROM project_agent_stream WHERE conversation_id=?"
2827
3403
  ).get(id).n
2828
3404
  )
2829
- };
3405
+ });
2830
3406
  }
2831
3407
  event(conversation, kind, data) {
2832
- if (this.hub.db.prepare("SELECT 1 FROM agent_contexts WHERE conversation_id=? AND kind<>'matter'").get(conversation))
3408
+ if (this.hub.db.prepare(
3409
+ "SELECT 1 FROM agent_contexts WHERE conversation_id=? AND kind NOT IN('matter','notice')"
3410
+ ).get(conversation))
2833
3411
  return;
2834
3412
  this.hub.db.prepare("INSERT INTO project_agent_stream(conversation_id,kind,data) VALUES(?,?,?)").run(conversation, kind, JSON.stringify(data));
2835
3413
  }
@@ -2838,24 +3416,34 @@ var AgentStore = class {
2838
3416
  "SELECT seq,kind,data FROM project_agent_stream WHERE conversation_id=? AND seq>? ORDER BY seq"
2839
3417
  ).all(conversation, after);
2840
3418
  }
2841
- ask(identity, text, chatId) {
3419
+ ask(identity, text, chatId, requestId) {
3420
+ if (chatId && this.hub.db.prepare("SELECT 1 FROM agent_contexts WHERE conversation_id=? AND kind='notice'").get(chatId))
3421
+ return new AgentNotices(this.hub).ask(identity, chatId, text, requestId);
2842
3422
  if (chatId && this.hub.db.prepare("SELECT 1 FROM agent_matters WHERE conversation_id=?").get(chatId))
2843
- return new Matters(this.hub).ask(identity, chatId, text);
2844
- return diagnosticTransaction(this.hub, () => {
2845
- const conversation = chatId ? this.ownedChat(identity, chatId) : this.conversation(identity.workspaceId, "instant", identity.memberId);
2846
- const id = randomUUID5(), date = now2();
2847
- this.hub.db.prepare(
2848
- "INSERT INTO project_agent_runs(id,conversation_id,text,created_at,updated_at) VALUES(?,?,?,?,?)"
2849
- ).run(id, conversation.id, text, date, date);
2850
- const value = this.runView(
2851
- this.hub.db.prepare("SELECT * FROM project_agent_runs WHERE id=?").get(id)
2852
- );
2853
- this.event(String(conversation.id), "run", value);
2854
- return value;
2855
- });
3423
+ return new Matters(this.hub).ask(identity, chatId, text, requestId);
3424
+ if (chatId) this.ownedChat(identity, chatId);
3425
+ return new AgentMessages(this.hub).request(
3426
+ requestId ? `chat:${identity.workspaceId}:${identity.memberId}:${requestId}` : void 0,
3427
+ { chatId, text },
3428
+ () => {
3429
+ const conversation = chatId ? this.ownedChat(identity, chatId) : this.conversation(identity.workspaceId, "instant", identity.memberId);
3430
+ const id = randomUUID7(), date = now2();
3431
+ this.hub.db.prepare(
3432
+ "INSERT INTO project_agent_runs(id,conversation_id,text,created_at,updated_at) VALUES(?,?,?,?,?)"
3433
+ ).run(id, conversation.id, text, date, date);
3434
+ const messages = new AgentMessages(this.hub);
3435
+ messages.input(String(conversation.id), id, text, identity.memberId);
3436
+ messages.answer(String(conversation.id), id, "", "queued");
3437
+ const value = this.runView(
3438
+ this.hub.db.prepare("SELECT * FROM project_agent_runs WHERE id=?").get(id)
3439
+ );
3440
+ this.event(String(conversation.id), "run", value);
3441
+ return value;
3442
+ }
3443
+ );
2856
3444
  }
2857
- enqueue(project, text, sourceKey = null, details = {}) {
2858
- return { taskId: enqueueInput(this.hub, project, text, sourceKey, details) };
3445
+ enqueue(project, text, sourceKey = null, details2 = {}) {
3446
+ return { taskId: enqueueInput(this.hub, project, text, sourceKey, details2) };
2859
3447
  }
2860
3448
  members(project) {
2861
3449
  return this.hub.db.prepare(
@@ -2889,19 +3477,25 @@ var AgentStore = class {
2889
3477
  db.prepare(
2890
3478
  `INSERT INTO project_agent_runs(id,conversation_id,task_id,text,created_at,updated_at)
2891
3479
  VALUES(?,?,?,?,?,?) ON CONFLICT(conversation_id,task_id) DO NOTHING`
2892
- ).run(randomUUID5(), lifetime.id, input.task_id, input.text, input.created_at, now2());
3480
+ ).run(randomUUID7(), lifetime.id, input.task_id, input.text, input.created_at, now2());
2893
3481
  }
2894
3482
  const run = db.prepare(
2895
3483
  `SELECT r.*,c.project_id,c.role,COALESCE(a.member_id,c.owner) owner,c.session_file,x.kind context_kind,x.scope_key context_scope,w.binding_id channel_binding,w.speaker FROM project_agent_runs r
2896
3484
  JOIN project_agent_conversations c ON c.id=r.conversation_id
2897
3485
  LEFT JOIN agent_contexts x ON x.conversation_id=c.id LEFT JOIN agent_run_actors a ON a.run_id=r.id LEFT JOIN agent_run_channels w ON w.run_id=r.id WHERE c.project_id=? AND r.state='queued' AND r.due_at<=?
3486
+ AND r.batch_run_id IS NULL AND (x.kind IS NULL OR x.kind<>'notice')
3487
+ AND (COALESCE(json_extract((SELECT hold_json FROM agent_matters WHERE conversation_id=r.conversation_id),'$.scope'),'')<>'all'
3488
+ OR json_extract((SELECT details_json FROM agent_run_details WHERE run_id=r.id),'$.origin.kind')='human')
2898
3489
  AND NOT EXISTS(SELECT 1 FROM project_agent_runs earlier WHERE earlier.conversation_id=r.conversation_id
2899
- AND earlier.rowid<r.rowid AND earlier.state IN('queued','running'))
3490
+ AND earlier.rowid<r.rowid AND earlier.state IN('queued','running')
3491
+ AND (COALESCE(json_extract((SELECT hold_json FROM agent_matters WHERE conversation_id=r.conversation_id),'$.scope'),'')<>'all'
3492
+ OR json_extract((SELECT details_json FROM agent_run_details WHERE run_id=earlier.id),'$.origin.kind')='human'))
2900
3493
  AND NOT EXISTS(SELECT 1 FROM project_agent_runs active WHERE active.conversation_id=r.conversation_id AND active.state='running')
2901
3494
  ORDER BY r.rowid LIMIT 1`
2902
3495
  ).get(project, at);
2903
3496
  if (!run) return void 0;
2904
- const token = randomUUID5();
3497
+ const text = batchInput(this.hub, project, run);
3498
+ const token = randomUUID7();
2905
3499
  db.prepare(
2906
3500
  "UPDATE project_agent_runs SET state='running',attempts=attempts+1,run_token=?,lease_until=?,updated_at=? WHERE id=?"
2907
3501
  ).run(token, at + 3e4, now2(), run.id);
@@ -2912,6 +3506,7 @@ var AgentStore = class {
2912
3506
  this.event(String(run.conversation_id), "state", { id: run.id, state: "running" });
2913
3507
  return {
2914
3508
  ...run,
3509
+ text,
2915
3510
  run_token: token,
2916
3511
  attempts: Number(run.attempts) + 1
2917
3512
  };
@@ -2937,16 +3532,22 @@ var AgentStore = class {
2937
3532
  if (!this.active(run)) return;
2938
3533
  this.hub.db.prepare("UPDATE project_agent_runs SET answer=answer||?,updated_at=? WHERE id=?").run(text, now2(), run.id);
2939
3534
  this.event(run.conversation_id, "delta", { id: run.id, text });
3535
+ new AgentMessages(this.hub).delta(run.id, text);
2940
3536
  });
2941
3537
  }
2942
3538
  resetAnswer(run) {
2943
3539
  if (!this.active(run)) return;
2944
3540
  this.hub.db.prepare("UPDATE project_agent_runs SET answer='' WHERE id=?").run(run.id);
2945
3541
  this.event(run.conversation_id, "reset", { id: run.id });
3542
+ new AgentMessages(this.hub).answer(run.conversation_id, run.id, "", "running");
2946
3543
  }
2947
3544
  finish(run, answer, error = null, retryable = false, diagnostic) {
2948
3545
  transaction(this.hub.db, () => {
2949
3546
  if (!this.active(run)) return;
3547
+ if (!error) {
3548
+ const draft = this.hub.db.prepare("SELECT body FROM agent_answer_drafts WHERE run_id=?").get(run.id);
3549
+ if (draft) answer = String(draft.body);
3550
+ }
2950
3551
  const retry = !!error && retryable && run.attempts <= 3;
2951
3552
  const state = retry ? "queued" : error ? "failed" : "succeeded";
2952
3553
  if (error && !answer)
@@ -2966,12 +3567,30 @@ var AgentStore = class {
2966
3567
  if (run.task_id !== null)
2967
3568
  this.hub.db.prepare("UPDATE project_agent_inbox SET state=? WHERE project_id=? AND task_id=?").run(state, run.project_id, run.task_id);
2968
3569
  this.event(run.conversation_id, "state", { id: run.id, state, error, answer });
3570
+ settleBatch(this.hub, run.id, state, error);
3571
+ new AgentMessages(this.hub).answer(run.conversation_id, run.id, answer, state);
3572
+ if (!error && run.context_kind === "matter") new Matters(this.hub).commitReports(run, answer);
3573
+ if (error) new Matters(this.hub).abandonDrafts(run.id);
3574
+ if (run.role === "lifetime") {
3575
+ const notice = inputDetails(this.hub, run.project_id, run.task_id).notification;
3576
+ if (notice) {
3577
+ const target = this.hub.db.prepare("SELECT target_id FROM agent_notice_threads WHERE conversation_id=?").get(notice.conversationId);
3578
+ if (!target?.target_id) {
3579
+ this.hub.db.prepare(
3580
+ "UPDATE project_agent_runs SET state=?,answer=?,error=?,updated_at=? WHERE id=?"
3581
+ ).run(state, answer, error, now2(), notice.runId);
3582
+ new AgentMessages(this.hub).answer(notice.conversationId, notice.runId, answer, state);
3583
+ }
3584
+ }
3585
+ }
2969
3586
  if (error && !retry && run.context_kind === "matter")
2970
3587
  new Matters(this.hub).report(
2971
3588
  run,
2972
3589
  {
3590
+ kind: "coordination",
2973
3591
  status: "waiting",
2974
- summary: "\u672C\u8F6E\u6267\u884C\u672A\u5B8C\u6210\uFF0C\u8BCA\u65AD\u5DF2\u8BB0\u5F55\u5728\u8FD0\u884C\u63D0\u9192\u4E2D\u3002\u8BF7\u6838\u5BF9\u539F\u56E0\u540E\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u3002"
3592
+ summary: "\u672C\u8F6E\u6267\u884C\u672A\u5B8C\u6210\uFF0C\u8BCA\u65AD\u5DF2\u8BB0\u5F55\u5728\u8FD0\u884C\u63D0\u9192\u4E2D\u3002\u8BF7\u6838\u5BF9\u539F\u56E0\u540E\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u3002",
3593
+ request: "\u8BF7\u6838\u5BF9\u7EC8\u6B62\u5931\u8D25\u7684\u539F\u56E0\uFF1B\u4E0D\u8981\u91CD\u590D\u6D3E\u53D1\u540C\u4E00\u5931\u8D25\u64CD\u4F5C\u3002"
2975
3594
  },
2976
3595
  `matter-failure:${run.id}`
2977
3596
  );
@@ -3015,7 +3634,7 @@ var AgentStore = class {
3015
3634
  ).get(sourceRunId) : null;
3016
3635
  const sourceAudit = source?.task_id != null ? inputDetails(this.hub, project, Number(source.task_id)).audit : void 0;
3017
3636
  const sourceUser = source?.owner || sourceAudit?.requester || null;
3018
- const id = randomUUID5();
3637
+ const id = randomUUID7();
3019
3638
  this.hub.db.prepare(
3020
3639
  `INSERT INTO project_agent_outbox
3021
3640
  (id,project_id,task_id,box,body,created_at,operation_key,source_run_id,source_user_id) VALUES(?,?,?,?,?,?,?,?,?)`
@@ -3032,6 +3651,9 @@ var AgentStore = class {
3032
3651
  );
3033
3652
  for (const user of valid)
3034
3653
  this.hub.db.prepare("INSERT INTO project_agent_recipients(message_id,user_id) VALUES(?,?)").run(id, user);
3654
+ if (value.to !== "debug")
3655
+ for (const user of valid)
3656
+ new AgentNotices(this.hub).ensure({ workspaceId: project, memberId: user }, id);
3035
3657
  return { messageId: id };
3036
3658
  }
3037
3659
  send(project, task, input, operationKey = null, sourceRunId = null) {
@@ -3055,9 +3677,11 @@ var AgentStore = class {
3055
3677
  createdAt: row.created_at,
3056
3678
  readAt: row.read_at,
3057
3679
  archivedAt: row.archived_at,
3680
+ conversationId: this.hub.db.prepare(
3681
+ "SELECT COALESCE(target_id,conversation_id) id FROM agent_notice_threads WHERE message_id=? AND user_id=?"
3682
+ ).get(id, identity.memberId)?.id ?? null,
3058
3683
  recipients: recipients.map((r) => ({
3059
- userId: r.user_id,
3060
- reply: r.reply_body === null ? null : { body: r.reply_body, createdAt: r.replied_at }
3684
+ userId: r.user_id
3061
3685
  }))
3062
3686
  });
3063
3687
  }
@@ -3069,6 +3693,17 @@ var AgentStore = class {
3069
3693
  const messages = rows.map((row) => this.message(identity, String(row.id)));
3070
3694
  return { messages, unread: messages.filter((m) => !m.readAt && !m.archivedAt).length };
3071
3695
  }
3696
+ unread(identity) {
3697
+ return new Matters(this.hub).unread(identity) + Number(
3698
+ this.hub.db.prepare(
3699
+ `SELECT COUNT(*) n FROM agent_notice_threads t
3700
+ JOIN project_agent_conversations c ON c.id=t.conversation_id WHERE c.project_id=? AND t.user_id=? AND t.target_id IS NULL
3701
+ AND EXISTS(SELECT 1 FROM agent_messages m WHERE m.conversation_id=t.conversation_id AND m.role='assistant' AND m.state='succeeded' AND m.body<>'' AND m.seq>t.read_seq)`
3702
+ ).get(identity.workspaceId, identity.memberId).n
3703
+ ) + this.messages(identity).messages.filter(
3704
+ (item) => item.box === "debug" && !item.readAt && !item.archivedAt
3705
+ ).length;
3706
+ }
3072
3707
  mark(identity, id, archive = false) {
3073
3708
  this.message(identity, id);
3074
3709
  this.hub.db.prepare(
@@ -3076,44 +3711,15 @@ var AgentStore = class {
3076
3711
  ).run(...archive ? [now2(), now2()] : [now2()], id, identity.memberId);
3077
3712
  return this.message(identity, id);
3078
3713
  }
3079
- reply(identity, id, body) {
3080
- return diagnosticTransaction(this.hub, () => {
3081
- const message = this.message(identity, id);
3082
- const recipient = this.hub.db.prepare("SELECT * FROM project_agent_recipients WHERE message_id=? AND user_id=?").get(id, identity.memberId);
3083
- if (recipient.reply_body === body) return { taskId: Number(recipient.reply_task_id) };
3084
- if (recipient.replied_at || recipient.archived_at)
3085
- throw new HubError(409, "OUTBOX_ARCHIVED", "\u8FD9\u6761\u6D88\u606F\u5DF2\u7ECF\u7ED3\u675F\u3002");
3086
- const source = this.hub.db.prepare(
3087
- `SELECT COALESCE(o.source_user_id,c.owner) owner,r.id FROM project_agent_outbox o
3088
- LEFT JOIN project_agent_runs r ON r.id=o.source_run_id
3089
- LEFT JOIN project_agent_conversations c ON c.id=r.conversation_id
3090
- WHERE o.id=? AND o.project_id=? AND (o.source_user_id IS NOT NULL OR (c.role='instant' AND c.owner<>''))`
3091
- ).get(id, identity.workspaceId);
3092
- const text = outboxReplyPrompt({
3093
- user: identity.memberId,
3094
- origin: source ? { owner: String(source.owner) } : null,
3095
- message: message.body,
3096
- reply: body
3097
- });
3098
- const taskId = enqueueInput(
3099
- this.hub,
3100
- identity.workspaceId,
3101
- text,
3102
- `reply:${id}:${identity.memberId}`,
3103
- {
3104
- audit: { messageId: id, sourceTaskId: message.taskId, requester: source?.owner ?? null }
3105
- }
3106
- );
3107
- this.hub.db.prepare(
3108
- "UPDATE project_agent_recipients SET reply_body=?,replied_at=?,archived_at=?,read_at=?,reply_task_id=? WHERE message_id=? AND user_id=?"
3109
- ).run(body, now2(), now2(), now2(), taskId, id, identity.memberId);
3110
- return { taskId };
3111
- });
3714
+ reply(identity, id, body, requestId) {
3715
+ this.message(identity, id);
3716
+ const conversation = new AgentNotices(this.hub).ensure(identity, id);
3717
+ return this.ask(identity, body, conversation, requestId);
3112
3718
  }
3113
3719
  };
3114
3720
 
3115
3721
  // apps/server/materials/store.ts
3116
- import { createHash as createHash6, randomUUID as randomUUID6 } from "node:crypto";
3722
+ import { createHash as createHash7, randomUUID as randomUUID8 } from "node:crypto";
3117
3723
  import { mkdir as mkdir2, readFile as readFile2, writeFile, rename, stat } from "node:fs/promises";
3118
3724
  import { join as join5 } from "node:path";
3119
3725
 
@@ -3230,7 +3836,7 @@ function requireProjectActor(hub, actor, owner) {
3230
3836
 
3231
3837
  // apps/server/materials/store.ts
3232
3838
  var exports = /* @__PURE__ */ new Map();
3233
- var fingerprint2 = (value) => createHash6("sha256").update(JSON.stringify(value)).digest("hex");
3839
+ var fingerprint2 = (value) => createHash7("sha256").update(JSON.stringify(value)).digest("hex");
3234
3840
  function materialHref(project, id, revision) {
3235
3841
  return `/projects/${encodeURIComponent(project)}/materials?${new URLSearchParams({ id, ...revision ? { revision: String(revision) } : {} })}`;
3236
3842
  }
@@ -3415,7 +4021,7 @@ var MaterialStore = class {
3415
4021
  const materials2 = this.all(project);
3416
4022
  await mkdir2(root, { recursive: true });
3417
4023
  const atomic = async (path, bytes) => {
3418
- const temp = `${path}.${randomUUID6()}.tmp`;
4024
+ const temp = `${path}.${randomUUID8()}.tmp`;
3419
4025
  await writeFile(temp, bytes, { mode: 292 });
3420
4026
  await rename(temp, path);
3421
4027
  };
@@ -3514,10 +4120,10 @@ async function gitCredentials(hub, project) {
3514
4120
  import { spawn } from "node:child_process";
3515
4121
  import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile3, rename as rename2, rm as rm2, mkdtemp as mkdtemp2, stat as stat2, rmdir } from "node:fs/promises";
3516
4122
  import { dirname as dirname4, join as join7 } from "node:path";
3517
- import { randomUUID as randomUUID8 } from "node:crypto";
4123
+ import { randomUUID as randomUUID10 } from "node:crypto";
3518
4124
 
3519
4125
  // apps/server/runtime-status.ts
3520
- import { randomUUID as randomUUID7 } from "node:crypto";
4126
+ import { randomUUID as randomUUID9 } from "node:crypto";
3521
4127
 
3522
4128
  // apps/server/deployment.ts
3523
4129
  function normalizePublicUrl(value) {
@@ -3682,7 +4288,7 @@ var RuntimeStatus = class {
3682
4288
  };
3683
4289
  }
3684
4290
  startWorker() {
3685
- const id = randomUUID7();
4291
+ const id = randomUUID9();
3686
4292
  observe(() => {
3687
4293
  this.hub.db.prepare(
3688
4294
  "DELETE FROM runtime_workers WHERE last_seen<? AND id<>(SELECT id FROM runtime_workers ORDER BY last_seen DESC LIMIT 1)"
@@ -3714,7 +4320,7 @@ var RuntimeStatus = class {
3714
4320
  };
3715
4321
  }
3716
4322
  startRepository(project, identity, fetching) {
3717
- const attempt = randomUUID7();
4323
+ const attempt = randomUUID9();
3718
4324
  observe(() => {
3719
4325
  this.hub.db.prepare(
3720
4326
  `INSERT INTO runtime_repositories VALUES(?,?,?,?,?,NULL)
@@ -3828,7 +4434,7 @@ var ProjectWorkspace = class {
3828
4434
  const target = join7(this.workspaces, path);
3829
4435
  await mkdir4(dirname4(target), { recursive: true });
3830
4436
  const bytes = await files.download({ workspaceId: this.project, memberId: "" }, hash3);
3831
- const temporary = `${target}.${randomUUID8()}.tmp`;
4437
+ const temporary = `${target}.${randomUUID10()}.tmp`;
3832
4438
  try {
3833
4439
  await writeFile3(temporary, bytes, { mode: 292 });
3834
4440
  await rename2(temporary, target);
@@ -4039,7 +4645,7 @@ var WorkspaceImports = class {
4039
4645
  `INSERT INTO project_workspace_imports VALUES (?,?,?,?,'pending',NULL,NULL)
4040
4646
  ON CONFLICT(project_id) DO UPDATE SET request_id=excluded.request_id,requester=excluded.requester,
4041
4647
  source_json=excluded.source_json,state='pending',result_json=NULL,error=NULL`
4042
- ).run(project, randomUUID9(), identity.memberId, json);
4648
+ ).run(project, randomUUID11(), identity.memberId, json);
4043
4649
  return this.status(project);
4044
4650
  }
4045
4651
  cancelRepository(project) {
@@ -4053,7 +4659,7 @@ var WorkspaceImports = class {
4053
4659
  "SELECT * FROM project_workspace_imports WHERE project_id=? AND state IN ('pending','running')"
4054
4660
  ).get(project);
4055
4661
  if (!row) return false;
4056
- const store = new AgentStore(this.hub), lease = randomUUID9();
4662
+ const store = new AgentStore(this.hub), lease = randomUUID11();
4057
4663
  if (!store.ownProject(project, lease)) return false;
4058
4664
  const cancelled = new AbortController(), active = AbortSignal.any([signal, cancelled.signal]);
4059
4665
  const current = () => !!this.hub.db.prepare(
@@ -4471,7 +5077,7 @@ var FileRepository = class {
4471
5077
  }
4472
5078
  async upload(authorization, device, hash3, bytes) {
4473
5079
  this.hub.authorize(authorization, device);
4474
- if (createHash7("sha256").update(bytes).digest("hex") !== HashSchema.parse(hash3))
5080
+ if (createHash8("sha256").update(bytes).digest("hex") !== HashSchema.parse(hash3))
4475
5081
  throw new HubError(400, "HASH_MISMATCH", "Content does not match its SHA-256.");
4476
5082
  const ref = await this.blobs.put(bytes);
4477
5083
  transaction(this.hub.db, () => {
@@ -4524,7 +5130,7 @@ var FileRepository = class {
4524
5130
  const head = this.head(identity, owner);
4525
5131
  if (head > 0 || publicationComplete(this.hub, identity.workspaceId, owner, head)) return false;
4526
5132
  this.writeVersion(identity, owner, {
4527
- operationId: randomUUID10(),
5133
+ operationId: randomUUID12(),
4528
5134
  parentRevision: 0,
4529
5135
  changes: entries.map((entry) => ({ kind: "upsert", ...entry }))
4530
5136
  });
@@ -4537,7 +5143,7 @@ var FileRepository = class {
4537
5143
  const changes = [...batch.changes].sort(
4538
5144
  (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0
4539
5145
  );
4540
- const fingerprint3 = createHash7("sha256").update(JSON.stringify({ parentRevision: batch.parentRevision, changes })).digest("hex");
5146
+ const fingerprint3 = createHash8("sha256").update(JSON.stringify({ parentRevision: batch.parentRevision, changes })).digest("hex");
4541
5147
  const workspace = identity.workspaceId;
4542
5148
  const prior = this.hub.db.prepare(
4543
5149
  "SELECT fingerprint, revision FROM file_operations WHERE workspace_id=? AND owner=? AND operation_id=?"
@@ -4774,7 +5380,7 @@ function taskSnapshotCurrent(hub, workspace, snapshot) {
4774
5380
  return taskInputComplete(hub, { workspaceId: workspace, memberId: snapshot.owner }) && Number(member?.file_revision) === snapshot.revision && Number(project?.file_revision ?? 0) === (snapshot.project?.revision ?? 0);
4775
5381
  }
4776
5382
  function snapshotDigest(files) {
4777
- return createHash8("sha256").update(JSON.stringify(files.map((f) => [f.path, f.sha256]))).digest("hex");
5383
+ return createHash9("sha256").update(JSON.stringify(files.map((f) => [f.path, f.sha256]))).digest("hex");
4778
5384
  }
4779
5385
  function captureScope(hub, identity, selection, allowEmpty = false) {
4780
5386
  const repository = new FileRepository(hub);
@@ -4821,7 +5427,7 @@ function captureSnapshot(hub, identity, selection, options = {}) {
4821
5427
  fingerprint: shared.fingerprint
4822
5428
  };
4823
5429
  if (shared.revision)
4824
- snapshot.fingerprint = createHash8("sha256").update(snapshot.fingerprint + ":" + shared.fingerprint).digest("hex");
5430
+ snapshot.fingerprint = createHash9("sha256").update(snapshot.fingerprint + ":" + shared.fingerprint).digest("hex");
4825
5431
  }
4826
5432
  return snapshot;
4827
5433
  }
@@ -4888,7 +5494,7 @@ var SnapshotReader = class _SnapshotReader {
4888
5494
  const entry = this.snapshot.files.find((f) => f.path === path);
4889
5495
  if (!entry) throw new HubError(403, "AI_SCOPE_VIOLATION", "File is outside the task snapshot.");
4890
5496
  const bytes = await new FileRepository(this.hub).download(this.identity, entry.sha256);
4891
- if (bytes.byteLength !== entry.size || createHash8("sha256").update(bytes).digest("hex") !== entry.sha256)
5497
+ if (bytes.byteLength !== entry.size || createHash9("sha256").update(bytes).digest("hex") !== entry.sha256)
4892
5498
  throw new HubError(500, "AI_CONTENT_CORRUPT", "Snapshot content verification failed.");
4893
5499
  return bytes;
4894
5500
  }
@@ -4975,8 +5581,8 @@ var SnapshotReader = class _SnapshotReader {
4975
5581
  };
4976
5582
 
4977
5583
  // apps/server/store.ts
4978
- import { createHash as createHash13, randomBytes as randomBytes7 } from "node:crypto";
4979
- import { join as join12 } from "node:path";
5584
+ import { createHash as createHash14, randomBytes as randomBytes7 } from "node:crypto";
5585
+ import { join as join13 } from "node:path";
4980
5586
  import { existsSync } from "node:fs";
4981
5587
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
4982
5588
 
@@ -5224,6 +5830,65 @@ CREATE TABLE agent_run_details (
5224
5830
  run_id TEXT PRIMARY KEY REFERENCES project_agent_runs(id) ON DELETE CASCADE, details_json TEXT NOT NULL
5225
5831
  );
5226
5832
  `;
5833
+ var agentCoordinationMigration = `
5834
+ ALTER TABLE project_agent_recipients DROP COLUMN reply_body;
5835
+ ALTER TABLE project_agent_recipients DROP COLUMN replied_at;
5836
+ ALTER TABLE project_agent_recipients DROP COLUMN reply_task_id;
5837
+ CREATE TABLE agent_messages (
5838
+ seq INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE,
5839
+ conversation_id TEXT NOT NULL REFERENCES project_agent_conversations(id),
5840
+ run_id TEXT REFERENCES project_agent_runs(id) ON DELETE SET NULL,
5841
+ role TEXT NOT NULL, author TEXT NOT NULL DEFAULT '', body TEXT NOT NULL,
5842
+ state TEXT NOT NULL DEFAULT 'succeeded', source TEXT,
5843
+ operation_key TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
5844
+ );
5845
+ CREATE INDEX agent_messages_conversation ON agent_messages(conversation_id,seq);
5846
+ CREATE TABLE agent_notice_threads (
5847
+ message_id TEXT NOT NULL REFERENCES project_agent_outbox(id), user_id TEXT NOT NULL,
5848
+ conversation_id TEXT NOT NULL UNIQUE REFERENCES project_agent_conversations(id),
5849
+ target_id TEXT REFERENCES agent_matters(conversation_id), read_seq INTEGER NOT NULL DEFAULT 0,
5850
+ PRIMARY KEY(message_id,user_id)
5851
+ );
5852
+ CREATE TABLE agent_message_requests (
5853
+ request_key TEXT PRIMARY KEY, fingerprint TEXT NOT NULL, result_json TEXT NOT NULL
5854
+ );
5855
+ CREATE TABLE agent_event_routes (
5856
+ project_id TEXT NOT NULL, input_key TEXT NOT NULL, branch TEXT NOT NULL DEFAULT '',
5857
+ conversation_id TEXT NOT NULL REFERENCES agent_matters(conversation_id),
5858
+ result_json TEXT NOT NULL, PRIMARY KEY(project_id,input_key,branch)
5859
+ );
5860
+ CREATE TABLE agent_matter_inputs (
5861
+ conversation_id TEXT NOT NULL REFERENCES agent_matters(conversation_id), input_key TEXT NOT NULL,
5862
+ run_id TEXT NOT NULL REFERENCES project_agent_runs(id) ON DELETE CASCADE,
5863
+ PRIMARY KEY(conversation_id,input_key)
5864
+ );
5865
+ CREATE TABLE agent_input_handbacks (
5866
+ run_id TEXT PRIMARY KEY REFERENCES project_agent_runs(id) ON DELETE CASCADE,
5867
+ conversation_id TEXT NOT NULL REFERENCES agent_matters(conversation_id),
5868
+ reason TEXT NOT NULL, task_id INTEGER NOT NULL
5869
+ );
5870
+ ALTER TABLE agent_matter_sources ADD COLUMN relation TEXT NOT NULL DEFAULT 'evidence';
5871
+ ALTER TABLE agent_matters ADD COLUMN goal TEXT NOT NULL DEFAULT '';
5872
+ ALTER TABLE agent_matters ADD COLUMN pending_json TEXT;
5873
+ ALTER TABLE agent_matters ADD COLUMN hold_json TEXT;
5874
+ ALTER TABLE agent_matters ADD COLUMN revision INTEGER NOT NULL DEFAULT 0;
5875
+ CREATE TABLE agent_matter_reports (
5876
+ id TEXT PRIMARY KEY, conversation_id TEXT NOT NULL REFERENCES agent_matters(conversation_id),
5877
+ run_id TEXT NOT NULL REFERENCES project_agent_runs(id) ON DELETE CASCADE,
5878
+ kind TEXT NOT NULL, status TEXT NOT NULL, summary TEXT NOT NULL, details_json TEXT NOT NULL,
5879
+ state TEXT NOT NULL DEFAULT 'pending', task_id INTEGER, created_at TEXT NOT NULL,
5880
+ operation_key TEXT NOT NULL UNIQUE
5881
+ );
5882
+ CREATE TABLE agent_answer_drafts (
5883
+ run_id TEXT PRIMARY KEY REFERENCES project_agent_runs(id) ON DELETE CASCADE,
5884
+ body TEXT NOT NULL
5885
+ );
5886
+ ALTER TABLE project_agent_runs ADD COLUMN batch_run_id TEXT REFERENCES project_agent_runs(id);
5887
+ CREATE TABLE agent_run_batches (
5888
+ run_id TEXT PRIMARY KEY REFERENCES project_agent_runs(id) ON DELETE CASCADE,
5889
+ text TEXT NOT NULL, details_json TEXT NOT NULL
5890
+ );
5891
+ `;
5227
5892
 
5228
5893
  // apps/server/diagnostics/schema.ts
5229
5894
  var diagnosticsMigration = `
@@ -5296,7 +5961,7 @@ CREATE INDEX device_connect_expiry ON device_connect_requests(expires_at);
5296
5961
  `;
5297
5962
 
5298
5963
  // apps/server/ai/task-writes.ts
5299
- import { createHash as createHash9, randomUUID as randomUUID11 } from "node:crypto";
5964
+ import { createHash as createHash10, randomUUID as randomUUID13 } from "node:crypto";
5300
5965
  import { z as z19 } from "zod";
5301
5966
  var taskWritesMigration = `
5302
5967
  CREATE TABLE task_cards_new (
@@ -5336,7 +6001,7 @@ var TaskWriteSchema = z19.object({
5336
6001
  ).min(1)
5337
6002
  }).strict();
5338
6003
  function writeTasks(hub, identity, operation, input) {
5339
- const value = TaskWriteSchema.parse(input), signature2 = createHash9("sha256").update(JSON.stringify(value)).digest("hex"), actor = identity.matterId ? JSON.stringify({ matter: identity.matterId, sender: identity.memberId || null }) : identity.group ? JSON.stringify({ group: identity.group, sender: identity.memberId }) : identity.memberId;
6004
+ const value = TaskWriteSchema.parse(input), signature2 = createHash10("sha256").update(JSON.stringify(value)).digest("hex"), actor = identity.matterId ? JSON.stringify({ matter: identity.matterId, sender: identity.memberId || null }) : identity.group ? JSON.stringify({ group: identity.group, sender: identity.memberId }) : identity.memberId;
5340
6005
  return diagnosticTransaction(hub, () => {
5341
6006
  requireProjectActor(hub, identity);
5342
6007
  const prior = hub.db.prepare("SELECT * FROM task_write_operations WHERE project_id=? AND operation_id=?").get(identity.workspaceId, operation);
@@ -5353,14 +6018,14 @@ function writeTasks(hub, identity, operation, input) {
5353
6018
  kind: "material",
5354
6019
  materialId: material.id,
5355
6020
  path: `materials/${material.id}/content.md`,
5356
- sha256: createHash9("sha256").update(material.content ?? JSON.stringify(material.originals)).digest("hex"),
6021
+ sha256: createHash10("sha256").update(material.content ?? JSON.stringify(material.originals)).digest("hex"),
5357
6022
  revision: material.revision,
5358
6023
  syncedAt: material.createdAt
5359
6024
  };
5360
6025
  });
5361
6026
  if (change.action === "create") {
5362
6027
  requireProjectActor(hub, identity, change.owner);
5363
- const id = randomUUID11(), fields2 = JSON.stringify(change.fields);
6028
+ const id = randomUUID13(), fields2 = JSON.stringify(change.fields);
5364
6029
  hub.db.prepare(
5365
6030
  `INSERT INTO task_cards(id,workspace_id,owner,job_id,fields_json,sources_json,synced_at,correction_json,origin) VALUES(?,?,?,NULL,?,?,?,?,'confirmed')`
5366
6031
  ).run(
@@ -5417,10 +6082,10 @@ function writeTasks(hub, identity, operation, input) {
5417
6082
  }
5418
6083
 
5419
6084
  // apps/server/wecom/groups.ts
5420
- import { createHash as createHash10, randomBytes as randomBytes6, randomUUID as randomUUID13 } from "node:crypto";
6085
+ import { createHash as createHash11, randomBytes as randomBytes6, randomUUID as randomUUID15 } from "node:crypto";
5421
6086
 
5422
6087
  // apps/server/agents/contexts.ts
5423
- import { randomUUID as randomUUID12 } from "node:crypto";
6088
+ import { randomUUID as randomUUID14 } from "node:crypto";
5424
6089
  var agentContextsMigration = `
5425
6090
  CREATE TABLE agent_contexts (
5426
6091
  conversation_id TEXT PRIMARY KEY REFERENCES project_agent_conversations(id),
@@ -5447,7 +6112,7 @@ var AgentContexts = class {
5447
6112
  context_scope: scope2
5448
6113
  })
5449
6114
  );
5450
- const id = randomUUID12();
6115
+ const id = randomUUID14();
5451
6116
  this.hub.db.prepare(
5452
6117
  `INSERT INTO project_agent_conversations(id,project_id,role,owner,created_at) VALUES(?,?,'instant','',?)`
5453
6118
  ).run(id, identity.workspaceId, (/* @__PURE__ */ new Date()).toISOString());
@@ -5470,7 +6135,7 @@ var AgentContexts = class {
5470
6135
  context_scope: String(context.scope_key)
5471
6136
  })
5472
6137
  );
5473
- const id = randomUUID12(), date = (/* @__PURE__ */ new Date()).toISOString();
6138
+ const id = randomUUID14(), date = (/* @__PURE__ */ new Date()).toISOString();
5474
6139
  this.hub.db.prepare(
5475
6140
  "INSERT INTO project_agent_runs(id,conversation_id,text,created_at,updated_at) VALUES(?,?,?,?,?)"
5476
6141
  ).run(id, conversation, text, date, date);
@@ -5677,7 +6342,7 @@ var commands = [
5677
6342
  { name: "use", usage: "/use <\u9879\u76EE\u540D\u79F0\u6216ID>", description: "\u5207\u6362\u4E2A\u4EBA\u5F53\u524D\u9879\u76EE", scope: "\u4EC5\u79C1\u804A" }
5678
6343
  ];
5679
6344
  function commandHelp() {
5680
- return commands.map((c) => `${c.usage} \u2014 ${c.description}${c.scope === "\u4EC5\u79C1\u804A" ? "\uFF08\u4EC5\u79C1\u804A\uFF09" : ""}`).join("\n") + "\n\n\u79C1\u804A\u53EF\u76F4\u63A5\u63D0\u95EE\uFF1B\u7FA4\u5185\u8BF7 @\u63D0\u767E\u4E8B\u3002\u901A\u77E5\u8BF7\u5F15\u7528\u539F\u6D88\u606F\u56DE\u590D\u3002\u76EE\u524D\u652F\u6301\u6587\u5B57\u548C\u94FE\u63A5\u3002";
6345
+ return commands.map((c) => `${c.usage} \u2014 ${c.description}${c.scope === "\u4EC5\u79C1\u804A" ? "\uFF08\u4EC5\u79C1\u804A\uFF09" : ""}`).join("\n") + "\n\n\u79C1\u804A\u53EF\u76F4\u63A5\u63D0\u95EE\uFF1B\u7FA4\u5185\u8BF7 @Tim\u3002\u901A\u77E5\u8BF7\u5F15\u7528\u539F\u6D88\u606F\u56DE\u590D\u3002\u76EE\u524D\u652F\u6301\u6587\u5B57\u548C\u94FE\u63A5\u3002";
5681
6346
  }
5682
6347
  function bindingWelcome(confirmation) {
5683
6348
  return `${confirmation}
@@ -5723,7 +6388,7 @@ CREATE TABLE wecom_group_codes(code_hash TEXT PRIMARY KEY,bot_id TEXT NOT NULL,p
5723
6388
  CREATE TABLE wecom_groups(bot_id TEXT NOT NULL,chat_id TEXT NOT NULL,project_id TEXT NOT NULL,conversation_id TEXT,bound_by TEXT NOT NULL,created_at TEXT NOT NULL,PRIMARY KEY(bot_id,chat_id));
5724
6389
  CREATE TABLE wecom_group_requests(run_id TEXT PRIMARY KEY,bot_id TEXT NOT NULL,chat_id TEXT NOT NULL,project_id TEXT NOT NULL,user_id TEXT NOT NULL,collected INTEGER NOT NULL DEFAULT 0);
5725
6390
  `;
5726
- var digest2 = (code) => createHash10("sha256").update(code.toUpperCase()).digest("hex");
6391
+ var digest2 = (code) => createHash11("sha256").update(code.toUpperCase()).digest("hex");
5727
6392
  function wecomParts(text) {
5728
6393
  const parts = [];
5729
6394
  let part = "", bytes = 0;
@@ -5788,7 +6453,7 @@ var WecomGroups = class {
5788
6453
  this.hub.db.prepare(
5789
6454
  `INSERT OR IGNORE INTO wecom_deliveries(id,bot_id,external_id,user_id,project_id,kind,source_id,body,created_at,chat_type) VALUES(?,?,?,?,?,?,?,?,?,2)`
5790
6455
  ).run(
5791
- randomUUID13(),
6456
+ randomUUID15(),
5792
6457
  bot,
5793
6458
  chat,
5794
6459
  identity?.memberId ?? null,
@@ -5865,7 +6530,7 @@ var WecomGroups = class {
5865
6530
  conversation = contexts.create(identity, "group", JSON.stringify([bot, chat]));
5866
6531
  this.hub.db.prepare("UPDATE wecom_groups SET conversation_id=? WHERE bot_id=? AND chat_id=?").run(conversation, bot, chat);
5867
6532
  if (command?.name === "new") {
5868
- notice("\u5DF2\u5F00\u59CB\u65B0\u8BDD\u9898\uFF0C\u8BF7 @\u63D0\u767E\u4E8B \u53D1\u9001\u95EE\u9898\u3002");
6533
+ notice("\u5DF2\u5F00\u59CB\u65B0\u8BDD\u9898\uFF0C\u8BF7 @Tim \u53D1\u9001\u95EE\u9898\u3002");
5869
6534
  return;
5870
6535
  }
5871
6536
  }
@@ -6105,12 +6770,12 @@ function sessionPageHref(projectId2, owner, sessionKey2, target = {}) {
6105
6770
  }
6106
6771
 
6107
6772
  // apps/server/sessions.ts
6108
- import { createHash as createHash12 } from "node:crypto";
6773
+ import { createHash as createHash13 } from "node:crypto";
6109
6774
 
6110
6775
  // packages/protocol/session-key.ts
6111
- import { createHash as createHash11 } from "node:crypto";
6776
+ import { createHash as createHash12 } from "node:crypto";
6112
6777
  function sessionKey(agent, nativeId) {
6113
- return createHash11("sha256").update(JSON.stringify([agent, nativeId])).digest("hex");
6778
+ return createHash12("sha256").update(JSON.stringify([agent, nativeId])).digest("hex");
6114
6779
  }
6115
6780
 
6116
6781
  // apps/server/sessions.ts
@@ -6119,6 +6784,21 @@ var SessionRepository = class {
6119
6784
  this.hub = hub;
6120
6785
  }
6121
6786
  hub;
6787
+ baseline(identity, owner, key) {
6788
+ if (identity.memberId !== owner)
6789
+ throw new HubError(403, "SESSION_OWNER_REQUIRED", "\u53EA\u80FD\u6838\u5BF9\u81EA\u5DF1\u7684\u91C7\u96C6\u57FA\u7EBF\u3002");
6790
+ const current = this.hub.db.prepare(
6791
+ "SELECT revision,hash FROM sessions WHERE workspace_id=? AND owner=? AND session_key=?"
6792
+ ).get(identity.workspaceId, owner, key);
6793
+ const cleared = current ? void 0 : this.hub.db.prepare(
6794
+ "SELECT revision,hash FROM reset_session_heads WHERE workspace_id=? AND owner=? AND session_key=?"
6795
+ ).get(identity.workspaceId, owner, key);
6796
+ return {
6797
+ revision: Number(current?.revision ?? cleared?.revision ?? 0),
6798
+ hash: current?.hash ?? cleared?.hash ?? null,
6799
+ cleared: !!cleared
6800
+ };
6801
+ }
6122
6802
  async commit(authorization, device, input) {
6123
6803
  const request = SessionCommitSchema.parse(input);
6124
6804
  const identity = this.hub.authorize(authorization, device);
@@ -6140,7 +6820,7 @@ var SessionRepository = class {
6140
6820
  owner: identity.memberId,
6141
6821
  key: request.sessionKey
6142
6822
  });
6143
- const fingerprint3 = createHash12("sha256").update(JSON.stringify([request.sessionKey, request.parentRevision, request.blob])).digest("hex");
6823
+ const fingerprint3 = createHash13("sha256").update(JSON.stringify([request.sessionKey, request.parentRevision, request.blob])).digest("hex");
6144
6824
  return transaction(this.hub.db, () => {
6145
6825
  const { workspaceId: workspace, memberId: owner } = this.hub.authorize(authorization, device);
6146
6826
  const db = this.hub.db;
@@ -6159,14 +6839,17 @@ var SessionRepository = class {
6159
6839
  const current = db.prepare(
6160
6840
  "SELECT revision, hash, title FROM sessions WHERE workspace_id=? AND owner=? AND session_key=?"
6161
6841
  ).get(workspace, owner, request.sessionKey);
6162
- const head = Number(current?.revision ?? 0);
6842
+ const cleared = current ? void 0 : db.prepare(
6843
+ "SELECT revision,hash FROM reset_session_heads WHERE workspace_id=? AND owner=? AND session_key=?"
6844
+ ).get(workspace, owner, request.sessionKey);
6845
+ const head = Number(current?.revision ?? cleared?.revision ?? 0);
6163
6846
  if (head !== request.parentRevision)
6164
6847
  throw new HubError(
6165
6848
  409,
6166
6849
  "STALE_REVISION",
6167
6850
  "Session changed on the server; inspect the current revision before retrying."
6168
6851
  );
6169
- const revision = current?.hash === request.blob.sha256 ? head : head + 1;
6852
+ const revision = (current?.hash ?? cleared?.hash) === request.blob.sha256 ? head : head + 1;
6170
6853
  if (revision !== head) {
6171
6854
  const info = {
6172
6855
  owner,
@@ -6541,7 +7224,7 @@ var GitHostError = class extends HubError {
6541
7224
  provider;
6542
7225
  upstreamStatus;
6543
7226
  };
6544
- var commentPrefix = "\u8BE5\u6D88\u606F\u7531\u63D0\u767E\u4E8B\u53D1\u9001\uFF0C\u76F8\u5173\u5185\u5BB9\u4EC5\u4F9B\u53C2\u8003";
7227
+ var commentPrefix = "\u8BE5\u6D88\u606F\u7531Tim\u53D1\u9001\uFF0C\u76F8\u5173\u5185\u5BB9\u4EC5\u4F9B\u53C2\u8003";
6545
7228
  var legacyCommentBody = (body, key) => `${body}
6546
7229
 
6547
7230
  <!-- teammy:${key} -->`;
@@ -6897,13 +7580,13 @@ var Gongfeng = class {
6897
7580
  }
6898
7581
  async read(number, signal, section = "details", by = "number") {
6899
7582
  const pr = await this.details(number, signal, by);
6900
- const details = { ...pr, ...this.summary(pr) };
7583
+ const details2 = { ...pr, ...this.summary(pr) };
6901
7584
  if (section === "comments") {
6902
7585
  const [comments, review2] = await Promise.all([
6903
7586
  this.pages(`merge_requests/${pr.id}/notes`, signal),
6904
7587
  this.request(`merge_request/${pr.id}/review`, signal)
6905
7588
  ]);
6906
- return { details, comments, review: review2 };
7589
+ return { details: details2, comments, review: review2 };
6907
7590
  }
6908
7591
  if (section === "diff") {
6909
7592
  const changes = await this.request(`merge_request/${pr.id}/changes`, signal);
@@ -6913,7 +7596,7 @@ var Gongfeng = class {
6913
7596
  (file) => file.is_too_large || file.is_collapse || typeof file.diff !== "string"
6914
7597
  );
6915
7598
  return {
6916
- details,
7599
+ details: details2,
6917
7600
  ...changes,
6918
7601
  incomplete,
6919
7602
  ...incomplete ? {
@@ -6921,7 +7604,7 @@ var Gongfeng = class {
6921
7604
  } : {}
6922
7605
  };
6923
7606
  }
6924
- return details;
7607
+ return details2;
6925
7608
  }
6926
7609
  async publish(number, body, key, taskId, signal) {
6927
7610
  const publications = new PrPublications(this.hub, this.project, this.config);
@@ -7186,12 +7869,23 @@ async function executeProjectQuery(hub, identity, name, input, signal, includeIn
7186
7869
  // apps/server/agents/personal-tools.ts
7187
7870
  import { z as z23 } from "zod";
7188
7871
  var personalQuerySchemas = {
7189
- list_messages: z23.object({}).strict(),
7190
- read_message: z23.object({ messageId: z23.uuid() }).strict()
7872
+ list_messages: z23.object({
7873
+ offset: z23.number().int().nonnegative().default(0),
7874
+ limit: z23.number().int().positive().default(20)
7875
+ }).strict(),
7876
+ read_message: z23.object({
7877
+ messageId: z23.uuid().optional(),
7878
+ conversationId: z23.uuid().optional(),
7879
+ after: z23.number().int().nonnegative().default(0),
7880
+ limit: z23.number().int().positive().default(20)
7881
+ }).strict().refine(
7882
+ (value) => !!value.messageId !== !!value.conversationId,
7883
+ "\u586B\u5199 messageId \u6216 conversationId"
7884
+ )
7191
7885
  };
7192
7886
  var personalQueryDescriptions = {
7193
- list_messages: "\u67E5\u8BE2\u5F53\u524D\u7528\u6237\u5728\u672C\u9879\u76EE\u6536\u5230\u7684\u63D0\u767E\u4E8B\u6D88\u606F\uFF0C\u5305\u62EC\u540E\u53F0\u59D4\u6258\u7ED3\u679C\u548C\u5F85\u56DE\u590D\u95EE\u9898\uFF1B\u4E0D\u4F1A\u6807\u8BB0\u5DF2\u8BFB\u3002",
7194
- read_message: "\u8BFB\u53D6\u5F53\u524D\u7528\u6237\u6536\u5230\u7684\u6307\u5B9A\u6D88\u606F\u53CA\u56DE\u590D\u72B6\u6001\uFF1B\u4E0D\u4F1A\u6807\u8BB0\u5DF2\u8BFB\u6216\u4FEE\u6539\u6D88\u606F\u3002"
7887
+ list_messages: "\u5206\u9875\u67E5\u8BE2\u5F53\u524D\u7528\u6237\u5728\u672C\u9879\u76EE\u6536\u5230\u7684\u901A\u77E5\u53CA\u53C2\u4E0E\u7684\u4E8B\u9879\uFF0C\u8FD4\u56DE\u53EF\u7EE7\u7EED\u7684 conversationId\uFF1B\u4E0D\u4F1A\u6807\u8BB0\u5DF2\u8BFB\u3002",
7888
+ read_message: "\u6309 messageId \u8BFB\u53D6\u901A\u77E5\uFF0C\u6216\u6309 conversationId \u8BFB\u53D6\u6709\u6743\u8BBF\u95EE\u7684\u4F1A\u8BDD\u6B63\u6587\uFF1Bafter \u662F\u6D88\u606F\u6E38\u6807\uFF0C\u4E0D\u4F1A\u6807\u8BB0\u5DF2\u8BFB\u3002"
7195
7889
  };
7196
7890
  function requirePersonalActor(hub, identity) {
7197
7891
  if (identity.group || identity.materialId || !identity.memberId)
@@ -7202,11 +7896,26 @@ function executePersonalQuery(hub, identity, name, input) {
7202
7896
  requirePersonalActor(hub, identity);
7203
7897
  const store = new AgentStore(hub);
7204
7898
  if (name === "list_messages") {
7205
- personalQuerySchemas.list_messages.parse(input);
7206
- return store.messages(identity);
7899
+ const { offset, limit } = personalQuerySchemas.list_messages.parse(input);
7900
+ const all = store.messages(identity), chats = store.chats(identity).chats;
7901
+ return {
7902
+ messages: all.messages.slice(offset, offset + limit),
7903
+ conversations: chats.slice(offset, offset + limit),
7904
+ unread: all.unread,
7905
+ nextOffset: offset + limit < Math.max(all.messages.length, chats.length) ? offset + limit : null
7906
+ };
7907
+ }
7908
+ if (name === "read_message") {
7909
+ const value = personalQuerySchemas.read_message.parse(input);
7910
+ if (value.messageId) return store.message(identity, value.messageId);
7911
+ store.ownedChat(identity, value.conversationId);
7912
+ const items = new AgentMessages(hub).list(value.conversationId, value.after, value.limit + 1), page = items.slice(0, value.limit);
7913
+ return {
7914
+ conversationId: value.conversationId,
7915
+ messages: page,
7916
+ next: items.length > value.limit ? page.at(-1).seq : null
7917
+ };
7207
7918
  }
7208
- if (name === "read_message")
7209
- return store.message(identity, personalQuerySchemas.read_message.parse(input).messageId);
7210
7919
  throw new HubError(404, "QUERY_NOT_FOUND", "\u67E5\u8BE2\u5DE5\u5177\u4E0D\u5B58\u5728\u3002");
7211
7920
  }
7212
7921
 
@@ -7228,8 +7937,8 @@ var schemas = {
7228
7937
  var descriptions = {
7229
7938
  ...queryDescriptions,
7230
7939
  ...personalQueryDescriptions,
7231
- ask_teammy: "\u5411\u672C\u9879\u76EE\u63D0\u767E\u4E8B\u63D0\u95EE\u6216\u59D4\u6258\u5DE5\u4F5C\uFF0C\u62E5\u6709\u4E0E\u7F51\u9875\u5BF9\u8BDD\u76F8\u540C\u7684\u5B8C\u6574\u80FD\u529B\u3002\u9996\u6B21\u4E0D\u586B conversationId \u521B\u5EFA\u72EC\u7ACB\u7F51\u9875\u5BF9\u8BDD\uFF0C\u540E\u7EED\u643A\u5E26\u8FD4\u56DE\u7684 ID \u7EE7\u7EED\u3002requestId \u4E3A\u672C\u6761\u6D88\u606F\u7684\u552F\u4E00\u6807\u8BC6\uFF0C\u91CD\u8BD5\u5FC5\u987B\u539F\u6837\u590D\u7528\u3002\u8FD4\u56DE queued/running \u65F6\u7528 get_teammy_reply \u67E5\u8BE2\u7ED3\u679C\uFF0C\u4E0D\u91CD\u590D\u53D1\u9001\u3002",
7232
- get_teammy_reply: "\u67E5\u8BE2\u6307\u5B9A\u63D0\u767E\u4E8B\u8BF7\u6C42\u7684\u6267\u884C\u72B6\u6001\u548C\u7B54\u590D\uFF0C\u4E0D\u53D1\u9001\u65B0\u6D88\u606F\u3002\u5931\u8D25\u65F6\u8FD4\u56DE error\uFF1B\u9700\u8981\u7528\u6237\u51B3\u5B9A\u65F6\u8F6C\u8FBE\u95EE\u9898\uFF0C\u5728\u540C\u4E00\u5BF9\u8BDD\u4F20\u56DE\u7528\u6237\u7B54\u590D\u3002"
7940
+ ask_teammy: "\u5411\u672C\u9879\u76EETim\u63D0\u95EE\u6216\u59D4\u6258\u5DE5\u4F5C\uFF0C\u62E5\u6709\u4E0E\u7F51\u9875\u5BF9\u8BDD\u76F8\u540C\u7684\u5B8C\u6574\u80FD\u529B\u3002\u9996\u6B21\u4E0D\u586B conversationId \u521B\u5EFA\u72EC\u7ACB\u7F51\u9875\u5BF9\u8BDD\uFF0C\u540E\u7EED\u643A\u5E26\u8FD4\u56DE\u7684 ID \u7EE7\u7EED\u3002requestId \u4E3A\u672C\u6761\u6D88\u606F\u7684\u552F\u4E00\u6807\u8BC6\uFF0C\u91CD\u8BD5\u5FC5\u987B\u539F\u6837\u590D\u7528\u3002\u8FD4\u56DE queued/running \u65F6\u7528 get_teammy_reply \u67E5\u8BE2\u7ED3\u679C\uFF0C\u4E0D\u91CD\u590D\u53D1\u9001\u3002",
7941
+ get_teammy_reply: "\u67E5\u8BE2\u6307\u5B9ATim\u8BF7\u6C42\u7684\u6267\u884C\u72B6\u6001\u548C\u7B54\u590D\uFF0C\u4E0D\u53D1\u9001\u65B0\u6D88\u606F\u3002\u5931\u8D25\u65F6\u8FD4\u56DE error\uFF1B\u9700\u8981\u7528\u6237\u51B3\u5B9A\u65F6\u8F6C\u8FBE\u95EE\u9898\uFF0C\u5728\u540C\u4E00\u5BF9\u8BDD\u4F20\u56DE\u7528\u6237\u7B54\u590D\u3002"
7233
7942
  };
7234
7943
  function externalToolCatalog() {
7235
7944
  return {
@@ -7277,8 +7986,9 @@ var ExternalTools = class {
7277
7986
  new AiDefaults(this.hub).resolve(identity.workspaceId);
7278
7987
  new AgentConfig(this.hub).initialize(identity.workspaceId);
7279
7988
  const store = new AgentStore(this.hub);
7280
- const chat = value.conversationId ?? store.createChat(identity).id;
7989
+ let chat = value.conversationId ?? store.createChat(identity).id;
7281
7990
  const run = store.ask(identity, value.text, chat);
7991
+ chat = run.conversationId ?? chat;
7282
7992
  this.hub.db.prepare("INSERT INTO agent_external_requests VALUES(?,?,?,?,?,?)").run(identity.workspaceId, identity.memberId, value.requestId, hash3, chat, run.id);
7283
7993
  const base = new SiteRepository(this.hub).config().publicServerUrl.replace(/\/+$/, "");
7284
7994
  return {
@@ -7302,7 +8012,7 @@ var ExternalTools = class {
7302
8012
  throw new HubError(
7303
8013
  403,
7304
8014
  "TOOL_UNAVAILABLE",
7305
- "\u672A\u5F00\u653E\u6B64\u5DE5\u5177\u3002\u4FEE\u6539\u9879\u76EE\u8BF7\u901A\u8FC7 ask_teammy \u59D4\u6258\u63D0\u767E\u4E8B\u3002"
8015
+ "\u672A\u5F00\u653E\u6B64\u5DE5\u5177\u3002\u4FEE\u6539\u9879\u76EE\u8BF7\u901A\u8FC7 ask_teammy \u59D4\u6258Tim\u3002"
7306
8016
  );
7307
8017
  }
7308
8018
  };
@@ -7319,6 +8029,169 @@ function registerExternalTools(server, hub) {
7319
8029
  });
7320
8030
  }
7321
8031
 
8032
+ // apps/server/maintenance.ts
8033
+ import { randomUUID as randomUUID16 } from "node:crypto";
8034
+ import { rm as rm3 } from "node:fs/promises";
8035
+ import { join as join12 } from "node:path";
8036
+ var maintenanceMigration = `
8037
+ CREATE TABLE data_baseline(id INTEGER PRIMARY KEY CHECK(id=1), generation TEXT NOT NULL, phase TEXT NOT NULL DEFAULT 'ready', reset_at TEXT);
8038
+ INSERT INTO data_baseline(id,generation) VALUES(1,lower(hex(randomblob(16))));
8039
+ CREATE TABLE runtime_api(id TEXT PRIMARY KEY, expires_at INTEGER NOT NULL);
8040
+ CREATE TABLE reset_session_heads(workspace_id TEXT NOT NULL,owner TEXT NOT NULL,session_key TEXT NOT NULL,revision INTEGER NOT NULL,hash TEXT NOT NULL,PRIMARY KEY(workspace_id,owner,session_key));
8041
+ CREATE TABLE reset_event_keys(project_id TEXT NOT NULL,source_key TEXT NOT NULL,PRIMARY KEY(project_id,source_key));
8042
+ CREATE TABLE reset_channel_posts(bot_id TEXT NOT NULL,topic_id TEXT NOT NULL,post_id TEXT NOT NULL,PRIMARY KEY(bot_id,topic_id,post_id));
8043
+ `;
8044
+ var Maintenance = class {
8045
+ constructor(hub) {
8046
+ this.hub = hub;
8047
+ }
8048
+ hub;
8049
+ baseline() {
8050
+ return this.hub.db.prepare("SELECT * FROM data_baseline WHERE id=1").get();
8051
+ }
8052
+ assertReady() {
8053
+ if (this.baseline().phase !== "ready")
8054
+ throw new HubError(
8055
+ 503,
8056
+ "MAINTENANCE",
8057
+ "\u6D4B\u8BD5\u6570\u636E\u6E05\u7406\u5C1A\u672A\u5B8C\u6210\uFF0C\u8BF7\u7EE7\u7EED\u6267\u884C\u7EF4\u62A4\u547D\u4EE4\u540E\u542F\u52A8\u670D\u52A1\u3002"
8058
+ );
8059
+ }
8060
+ apiLease() {
8061
+ this.assertReady();
8062
+ const id = randomUUID16();
8063
+ const beat = () => this.hub.db.prepare(
8064
+ "INSERT INTO runtime_api VALUES(?,?) ON CONFLICT(id) DO UPDATE SET expires_at=excluded.expires_at"
8065
+ ).run(id, Date.now() + 3e4);
8066
+ beat();
8067
+ const timer = setInterval(() => {
8068
+ try {
8069
+ beat();
8070
+ } catch {
8071
+ }
8072
+ }, 5e3);
8073
+ timer.unref();
8074
+ return () => {
8075
+ clearInterval(timer);
8076
+ this.hub.db.prepare("DELETE FROM runtime_api WHERE id=?").run(id);
8077
+ };
8078
+ }
8079
+ /** Explicit offline maintenance, never part of an ordinary schema upgrade. */
8080
+ async resetTestData() {
8081
+ const db = this.hub.db;
8082
+ transaction(db, () => {
8083
+ const active = db.prepare(
8084
+ `SELECT 1 FROM runtime_api WHERE expires_at>?
8085
+ UNION ALL SELECT 1 FROM runtime_workers WHERE stopped=0 AND last_seen>?
8086
+ UNION ALL SELECT 1 FROM wecom_runtime WHERE expires_at>?
8087
+ UNION ALL SELECT 1 FROM project_agent_leases WHERE expires_at>? LIMIT 1`
8088
+ ).get(Date.now(), Date.now() - 3e4, Date.now(), Date.now());
8089
+ if (active)
8090
+ throw new HubError(
8091
+ 409,
8092
+ "MAINTENANCE_BUSY",
8093
+ "\u8BF7\u5148\u505C\u6B62 API\u3001Worker \u548C\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\uFF1B\u9000\u51FA\u540E\u7684\u79DF\u7EA6\u6700\u591A\u9700\u8981 30 \u79D2\u91CA\u653E\u3002"
8094
+ );
8095
+ if (this.baseline().phase === "ready")
8096
+ db.prepare(
8097
+ "UPDATE data_baseline SET generation=?,phase='clearing',reset_at=? WHERE id=1"
8098
+ ).run(randomUUID16(), (/* @__PURE__ */ new Date()).toISOString());
8099
+ });
8100
+ transaction(db, () => {
8101
+ db.exec(`INSERT OR REPLACE INTO reset_session_heads SELECT workspace_id,owner,session_key,revision,hash FROM sessions;
8102
+ INSERT OR IGNORE INTO reset_event_keys SELECT project_id,source_key FROM project_agent_inbox WHERE source_key IS NOT NULL AND source_key<>'initial';
8103
+ INSERT OR IGNORE INTO reset_channel_posts SELECT bot_id,topic_id,post_id FROM wecom_matter_threads WHERE post_id IS NOT NULL;
8104
+ UPDATE wecom_subscriptions SET chat_id=NULL,outbox_after=0;
8105
+ UPDATE wecom_groups SET conversation_id=NULL;
8106
+ UPDATE wecom_channel_messages SET run_id=NULL;
8107
+ PRAGMA defer_foreign_keys=ON;`);
8108
+ const tables = [
8109
+ "agent_notice_threads",
8110
+ "agent_event_routes",
8111
+ "agent_messages",
8112
+ "agent_message_requests",
8113
+ "agent_external_requests",
8114
+ "agent_references",
8115
+ "agent_input_details",
8116
+ "agent_matter_inputs",
8117
+ "agent_input_handbacks",
8118
+ "agent_matter_reports",
8119
+ "agent_answer_drafts",
8120
+ "agent_run_batches",
8121
+ "agent_matter_sources",
8122
+ "agent_matter_members",
8123
+ "agent_matter_operations",
8124
+ "wecom_matter_threads",
8125
+ "agent_matters",
8126
+ "project_agent_recipients",
8127
+ "project_agent_outbox",
8128
+ "project_agent_publications",
8129
+ "project_agent_runs",
8130
+ "project_agent_stream",
8131
+ "agent_contexts",
8132
+ "project_agent_conversations",
8133
+ "project_agent_inbox",
8134
+ "project_agent_sources",
8135
+ "project_agent_leases",
8136
+ "wecom_requests",
8137
+ "wecom_group_requests",
8138
+ "wecom_deliveries",
8139
+ "wecom_channel_events",
8140
+ "wecom_codes",
8141
+ "wecom_group_codes",
8142
+ "wecom_runtime",
8143
+ "task_write_operations",
8144
+ "task_cards",
8145
+ "task_board_members",
8146
+ "ai_events",
8147
+ "ai_attempts",
8148
+ "ai_operations",
8149
+ "ai_jobs",
8150
+ "session_analysis_results",
8151
+ "session_analysis_jobs",
8152
+ "session_content",
8153
+ "session_fts",
8154
+ "session_messages",
8155
+ "session_revisions",
8156
+ "sessions",
8157
+ "materials",
8158
+ "material_versions",
8159
+ "resource_documents",
8160
+ "diagnostic_records",
8161
+ "diagnostic_conditions",
8162
+ "runtime_repositories",
8163
+ "runtime_sandbox",
8164
+ "runtime_workers",
8165
+ "runtime_api"
8166
+ ];
8167
+ for (const table of tables) db.exec(`DELETE FROM ${table}`);
8168
+ if (db.prepare("PRAGMA foreign_key_check").all().length)
8169
+ throw new Error("Reset would leave invalid references. No business rows were removed.");
8170
+ });
8171
+ for (const name of ["materials", "logs"])
8172
+ await rm3(join12(this.hub.directory, name), { recursive: true, force: true });
8173
+ const projects = db.prepare("SELECT id FROM projects").all();
8174
+ for (const project of projects) {
8175
+ const root = join12(this.hub.directory, "project-agents", String(project.id));
8176
+ for (const name of [
8177
+ "lifetime",
8178
+ "instant",
8179
+ "constant",
8180
+ "contexts",
8181
+ "workspaces",
8182
+ "files.json"
8183
+ ])
8184
+ await rm3(join12(root, name), { recursive: true, force: true });
8185
+ }
8186
+ db.prepare("UPDATE data_baseline SET phase='ready' WHERE id=1").run();
8187
+ return {
8188
+ generation: String(this.baseline().generation),
8189
+ projects: projects.length,
8190
+ retained: "accounts, memberships, connections, credentials and workspace sync baseline"
8191
+ };
8192
+ }
8193
+ };
8194
+
7322
8195
  // apps/server/ai/schema.ts
7323
8196
  var aiConfigMigration = `
7324
8197
  CREATE TABLE ai_profiles (
@@ -7354,7 +8227,7 @@ ALTER TABLE ai_jobs ADD COLUMN next_retry_at INTEGER;
7354
8227
 
7355
8228
  // apps/server/store.ts
7356
8229
  function digest3(token) {
7357
- return createHash13("sha256").update(token).digest("hex");
8230
+ return createHash14("sha256").update(token).digest("hex");
7358
8231
  }
7359
8232
  var migrations = [
7360
8233
  String.raw`
@@ -7401,15 +8274,18 @@ var migrations = [
7401
8274
  externalToolsMigration,
7402
8275
  agentEventsMigration,
7403
8276
  agentMattersMigration,
7404
- wecomChannelsMigration
8277
+ wecomChannelsMigration,
8278
+ agentCoordinationMigration,
8279
+ maintenanceMigration
7405
8280
  ];
7406
8281
  var HubStore = class {
7407
8282
  constructor(directory) {
7408
8283
  this.directory = directory;
7409
- const path = join12(directory, "hub.sqlite3");
8284
+ const path = join13(directory, "hub.sqlite3");
7410
8285
  if (existsSync(path)) {
7411
8286
  const existing = new DatabaseSync2(path, { readOnly: true });
7412
8287
  try {
8288
+ existing.exec("PRAGMA busy_timeout=5000");
7413
8289
  const version2 = Number(existing.prepare("PRAGMA user_version").get().user_version);
7414
8290
  if (version2 > 0 && version2 < 9)
7415
8291
  throw new Error(
@@ -7497,10 +8373,10 @@ var HubStore = class {
7497
8373
  };
7498
8374
 
7499
8375
  // apps/server/ai/jobs.ts
7500
- import { createHash as createHash15, randomUUID as randomUUID15 } from "node:crypto";
8376
+ import { createHash as createHash16, randomUUID as randomUUID18 } from "node:crypto";
7501
8377
 
7502
8378
  // apps/server/ai/runtime-resources.ts
7503
- import { createHash as createHash14 } from "node:crypto";
8379
+ import { createHash as createHash15 } from "node:crypto";
7504
8380
  import { readFileSync as readFileSync5 } from "node:fs";
7505
8381
  import { resolve as resolve3 } from "node:path";
7506
8382
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -7583,7 +8459,7 @@ function loadWorkflow(workflow, directory = process.env.TEAMMY_AI_RUNTIME_DIR) {
7583
8459
  throw new Error("Missing tool description");
7584
8460
  return WorkflowRuntimeSchema.parse({
7585
8461
  ...resource,
7586
- fingerprint: createHash14("sha256").update(JSON.stringify(resource)).digest("hex")
8462
+ fingerprint: createHash15("sha256").update(JSON.stringify(resource)).digest("hex")
7587
8463
  });
7588
8464
  } catch {
7589
8465
  throw new HubError(
@@ -7603,7 +8479,7 @@ ${JSON.stringify(z25.toJSONSchema(schema))}`;
7603
8479
  }
7604
8480
 
7605
8481
  // apps/server/ai/task-store.ts
7606
- import { randomUUID as randomUUID14 } from "node:crypto";
8482
+ import { randomUUID as randomUUID17 } from "node:crypto";
7607
8483
  var fields = (row) => JSON.parse(String(row.fields_json));
7608
8484
  var sources = (row) => JSON.parse(String(row.sources_json));
7609
8485
  var paths = (value) => JSON.stringify(value.map((s) => s.path).sort());
@@ -7662,7 +8538,7 @@ function applyTaskResult(hub, job, result) {
7662
8538
  if (oldGroup.length === 1 && newGroup.length === 1 && !used.has(String(oldGroup[0].id)) && !explicit.has(String(oldGroup[0].id)))
7663
8539
  old = oldGroup[0];
7664
8540
  }
7665
- const id = old ? String(old.id) : randomUUID14();
8541
+ const id = old ? String(old.id) : randomUUID17();
7666
8542
  used.add(id);
7667
8543
  const { sources: fileSources, taskId: _taskId, ...content } = task;
7668
8544
  const source = [
@@ -7916,7 +8792,7 @@ var AiJobs = class {
7916
8792
  this.hub.db.prepare("INSERT INTO ai_events(job_id,attempt,kind,message,created_at) VALUES(?,?,?,?,?)").run(id, attempt, kind, message.slice(0, 2e3), now3());
7917
8793
  }
7918
8794
  operation(session, operationId, input, action) {
7919
- const fingerprint3 = createHash15("sha256").update(JSON.stringify(input)).digest("hex");
8795
+ const fingerprint3 = createHash16("sha256").update(JSON.stringify(input)).digest("hex");
7920
8796
  return transaction(this.hub.db, () => {
7921
8797
  const old = this.hub.db.prepare(
7922
8798
  "SELECT * FROM ai_operations WHERE workspace_id=? AND requester=? AND operation_id=?"
@@ -7942,7 +8818,7 @@ var AiJobs = class {
7942
8818
  return this.enqueue(
7943
8819
  identity,
7944
8820
  {
7945
- operationId: randomUUID15(),
8821
+ operationId: randomUUID18(),
7946
8822
  workflow: "task-bootstrap",
7947
8823
  question: "",
7948
8824
  selection: { owner: identity.memberId, kind: "directory", path: "" }
@@ -8029,7 +8905,7 @@ var AiJobs = class {
8029
8905
  const runtime = resource ?? loadWorkflow(input.workflow);
8030
8906
  if (runtime.workflow !== input.workflow)
8031
8907
  throw new HubError(500, "AI_RUNTIME_INVALID", "Workflow resource mismatch.");
8032
- const id2 = randomUUID15(), date = now3();
8908
+ const id2 = randomUUID18(), date = now3();
8033
8909
  const taskContext = input.workflow === "task-bootstrap" ? captureTaskContext(this.hub, session.workspaceId, snapshot) : null;
8034
8910
  this.hub.db.prepare(
8035
8911
  "INSERT INTO ai_jobs(id,workspace_id,requester,request_json,profile_json,snapshot_json,prompt_version,status,created_at,updated_at,runtime_json,origin,task_context_json) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)"
@@ -8147,7 +9023,7 @@ var AiJobs = class {
8147
9023
  "SELECT * FROM ai_jobs WHERE status='queued' AND (next_retry_at IS NULL OR next_retry_at<=?) ORDER BY created_at,id LIMIT 1"
8148
9024
  ).get(at);
8149
9025
  if (!row) return void 0;
8150
- const token = randomUUID15(), attempt = Number(row.attempt_count) + 1, date = now3();
9026
+ const token = randomUUID18(), attempt = Number(row.attempt_count) + 1, date = now3();
8151
9027
  this.hub.db.prepare(
8152
9028
  "UPDATE ai_jobs SET status='running',attempt_count=?,run_token=?,lease_until=?,updated_at=?,error=NULL,next_retry_at=NULL WHERE id=?"
8153
9029
  ).run(attempt, token, at + 3e4, date, row.id);
@@ -8282,9 +9158,9 @@ var AiJobs = class {
8282
9158
  };
8283
9159
 
8284
9160
  // apps/server/ai/pi-runner.ts
8285
- import { mkdtemp as mkdtemp4, rm as rm4 } from "node:fs/promises";
9161
+ import { mkdtemp as mkdtemp4, rm as rm5 } from "node:fs/promises";
8286
9162
  import { tmpdir as tmpdir2 } from "node:os";
8287
- import { join as join15 } from "node:path";
9163
+ import { join as join16 } from "node:path";
8288
9164
  import { InMemoryCredentialStore, InMemoryModelsStore } from "@earendil-works/pi-ai";
8289
9165
 
8290
9166
  // apps/server/ai/task-tools.ts
@@ -8454,9 +9330,9 @@ function analysisTools(reader, signal, progress, runtime, explorer) {
8454
9330
 
8455
9331
  // apps/server/ai/native-sandbox.ts
8456
9332
  import { spawn as spawn2 } from "node:child_process";
8457
- import { mkdtemp as mkdtemp3, rm as rm3 } from "node:fs/promises";
9333
+ import { mkdtemp as mkdtemp3, rm as rm4 } from "node:fs/promises";
8458
9334
  import { tmpdir } from "node:os";
8459
- import { basename, dirname as dirname6, join as join14 } from "node:path";
9335
+ import { basename, dirname as dirname6, join as join15 } from "node:path";
8460
9336
  import { fileURLToPath as fileURLToPath3 } from "node:url";
8461
9337
 
8462
9338
  // apps/server/ai/agent-channel.ts
@@ -8497,7 +9373,7 @@ var AgentChannel = class {
8497
9373
 
8498
9374
  // apps/server/ai/agent-workspace.ts
8499
9375
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
8500
- import { dirname as dirname5, join as join13 } from "node:path";
9376
+ import { dirname as dirname5, join as join14 } from "node:path";
8501
9377
 
8502
9378
  // apps/server/ai/line-diff.ts
8503
9379
  import { Worker } from "node:worker_threads";
@@ -8670,13 +9546,13 @@ var TaskExplorer = class {
8670
9546
  // apps/server/ai/agent-workspace.ts
8671
9547
  async function materializeWorkspace(root, reader, signal) {
8672
9548
  const owner = FileOwnerSchema.parse(reader.snapshot.owner);
8673
- await mkdir5(join13(root, owner), { recursive: true });
9549
+ await mkdir5(join14(root, owner), { recursive: true });
8674
9550
  async function copy(source, prefix, shared = false) {
8675
9551
  for (const file of source.snapshot.files) {
8676
9552
  signal.throwIfAborted();
8677
9553
  const path = FilePathSchema.parse(file.path);
8678
9554
  if (shared && !sharedPathAllowed(path, DEFAULT_SHARED_PATHS)) continue;
8679
- const target = join13(root, prefix, path);
9555
+ const target = join14(root, prefix, path);
8680
9556
  await mkdir5(dirname5(target), { recursive: true });
8681
9557
  await writeFile5(target, await source.bytes(path), { flag: "wx", mode: 292 });
8682
9558
  }
@@ -8866,21 +9742,21 @@ async function sandboxCommand(root, baseline) {
8866
9742
  fileURLToPath3(new URL("./native-pi-entry.js", import.meta.url)),
8867
9743
  "/opt/teammy/native-pi-entry.mjs",
8868
9744
  "--ro-bind",
8869
- join14(root, "workspaces"),
9745
+ join15(root, "workspaces"),
8870
9746
  "/workspaces"
8871
9747
  );
8872
- if (baseline) args.push("--ro-bind", join14(root, "baseline/workspaces"), "/baseline/workspaces");
9748
+ if (baseline) args.push("--ro-bind", join15(root, "baseline/workspaces"), "/baseline/workspaces");
8873
9749
  args.push("--chdir", "/workspaces", "/opt/node-bin/node", "/opt/teammy/native-pi-entry.mjs");
8874
9750
  return { command: "bwrap", args, env: resources.env };
8875
9751
  }
8876
9752
  async function runNativeAgent(profile, reader, key, signal, progress, runtime, systemPrompt, input, context = null) {
8877
9753
  signal.throwIfAborted();
8878
- const root = await mkdtemp3(join14(tmpdir(), "teammy-agent-"));
9754
+ const root = await mkdtemp3(join15(tmpdir(), "teammy-agent-"));
8879
9755
  try {
8880
- await materializeWorkspace(join14(root, "workspaces"), reader, signal);
9756
+ await materializeWorkspace(join15(root, "workspaces"), reader, signal);
8881
9757
  if (context?.baseline)
8882
9758
  await materializeWorkspace(
8883
- join14(root, "baseline/workspaces"),
9759
+ join15(root, "baseline/workspaces"),
8884
9760
  reader.forSnapshot(context.baseline),
8885
9761
  signal
8886
9762
  );
@@ -8995,7 +9871,7 @@ async function runNativeAgent(profile, reader, key, signal, progress, runtime, s
8995
9871
  await Promise.allSettled(pending);
8996
9872
  }
8997
9873
  } finally {
8998
- await rm3(root, { recursive: true, force: true });
9874
+ await rm4(root, { recursive: true, force: true });
8999
9875
  }
9000
9876
  }
9001
9877
 
@@ -9020,7 +9896,7 @@ async function runAgent(profile, reader, key, signal, progress, runtime, systemP
9020
9896
  SessionManager,
9021
9897
  SettingsManager
9022
9898
  } = await import("@earendil-works/pi-coding-agent");
9023
- const cwd = await mkdtemp4(join15(tmpdir2(), "teammy-ai-run-"));
9899
+ const cwd = await mkdtemp4(join16(tmpdir2(), "teammy-ai-run-"));
9024
9900
  try {
9025
9901
  const { models, model } = modelAccess(profile, key, runtime.config.thinkingLevel !== "off");
9026
9902
  const modelRuntime = await ModelRuntime.create({
@@ -9110,7 +9986,7 @@ async function runAgent(profile, reader, key, signal, progress, runtime, systemP
9110
9986
  session.dispose();
9111
9987
  }
9112
9988
  } finally {
9113
- await rm4(cwd, { recursive: true, force: true });
9989
+ await rm5(cwd, { recursive: true, force: true });
9114
9990
  }
9115
9991
  }
9116
9992
 
@@ -9382,8 +10258,8 @@ function scheduleTaskBootstraps(hub, at = Date.now()) {
9382
10258
  }
9383
10259
 
9384
10260
  // apps/server/agents/runtime.ts
9385
- import { rm as rm5 } from "node:fs/promises";
9386
- import { join as join17 } from "node:path";
10261
+ import { rm as rm6 } from "node:fs/promises";
10262
+ import { join as join18 } from "node:path";
9387
10263
 
9388
10264
  // apps/server/materials/prompts.ts
9389
10265
  var materialPrompt = `## \u804C\u8D23
@@ -9407,20 +10283,20 @@ var groupPrompt = `## \u5F53\u524D\u5165\u53E3\uFF1A\u9879\u76EE\u7FA4
9407
10283
  \u4EFB\u52A1\u8C03\u6574\u5148\u8BF4\u660E\u5177\u4F53\u5EFA\u8BAE\uFF0C\u7528\u6237\u660E\u786E\u786E\u8BA4\u540E\u624D\u4F7F\u7528 write_tasks\u3002\u786E\u8BA4\u9488\u5BF9\u5DF2\u8BA8\u8BBA\u7684\u8C03\u6574\uFF0C\u4E0D\u6269\u5C55\u4E3A\u4EFB\u610F\u4EFB\u52A1\u4FEE\u6539\u3002`;
9408
10284
 
9409
10285
  // apps/server/agents/runtime.ts
9410
- import { randomUUID as randomUUID16 } from "node:crypto";
10286
+ import { randomUUID as randomUUID19 } from "node:crypto";
9411
10287
  import { setTimeout as delay3 } from "node:timers/promises";
9412
10288
  import { z as z34 } from "zod";
9413
10289
 
9414
10290
  // apps/server/agents/sandbox.ts
9415
10291
  import { spawn as spawn3 } from "node:child_process";
9416
10292
  import { mkdir as mkdir6, rename as rename3 } from "node:fs/promises";
9417
- import { basename as basename2, dirname as dirname7, join as join16 } from "node:path";
10293
+ import { basename as basename2, dirname as dirname7, join as join17 } from "node:path";
9418
10294
  import { fileURLToPath as fileURLToPath4 } from "node:url";
9419
10295
  async function projectAgentStateDirectory(root, role) {
9420
- const state = join16(root, role);
10296
+ const state = join17(root, role);
9421
10297
  if (role === "instant") {
9422
10298
  try {
9423
- await rename3(join16(root, "constant"), state);
10299
+ await rename3(join17(root, "constant"), state);
9424
10300
  } catch (error) {
9425
10301
  if (error.code !== "ENOENT") throw error;
9426
10302
  }
@@ -9441,13 +10317,13 @@ async function projectSandboxCommand(workspace, role, contextId, writable = fals
9441
10317
  if (parent === dependencies) throw new Error("Pi dependencies not found");
9442
10318
  dependencies = parent;
9443
10319
  }
9444
- const state = contextId ? join16(workspace.root, "contexts", contextId) : await projectAgentStateDirectory(workspace.root, role);
10320
+ const state = contextId ? join17(workspace.root, "contexts", contextId) : await projectAgentStateDirectory(workspace.root, role);
9445
10321
  await mkdir6(state, { recursive: true, mode: 448 });
9446
10322
  const args = await sandboxSystemArgs();
9447
10323
  const resources = await sandboxResources();
9448
10324
  args.push(...resources.args);
9449
10325
  if (writable) {
9450
- const work = join16(state, "work");
10326
+ const work = join17(state, "work");
9451
10327
  await mkdir6(work, { recursive: true, mode: 448 });
9452
10328
  args.push("--bind", work, "/work");
9453
10329
  }
@@ -9616,19 +10492,35 @@ var coordinationSchemas = {
9616
10492
  latest: z31.boolean().optional(),
9617
10493
  section: z31.enum(["details", "comments", "diff", "overview", "handoff"]).optional()
9618
10494
  }).strict(),
9619
- query_matters: z31.object({ status: z31.enum(["active", "waiting", "completed"]).optional() }).strict(),
9620
- read_matter: z31.object({ ref: z31.string() }).strict(),
10495
+ query_matters: z31.object({
10496
+ status: z31.enum(["active", "waiting", "completed"]).optional(),
10497
+ query: z31.string().optional(),
10498
+ member: z31.string().optional(),
10499
+ source: z31.string().optional(),
10500
+ offset: z31.number().int().nonnegative().default(0),
10501
+ limit: z31.number().int().positive().default(20)
10502
+ }).strict(),
10503
+ read_matter: z31.object({
10504
+ ref: z31.string(),
10505
+ section: z31.enum(["overview", "messages", "reports"]).default("overview"),
10506
+ after: z31.number().int().nonnegative().default(0),
10507
+ limit: z31.number().int().positive().default(20)
10508
+ }).strict(),
9621
10509
  delegate_matter: DelegateMatterSchema,
9622
10510
  report_matter: ReportMatterSchema,
10511
+ handback_input: z31.object({ reason: z31.string().trim().min(1) }).strict(),
10512
+ set_matter_hold: MatterHoldSchema,
9623
10513
  notify_members: z31.object({ members: z31.array(z31.string().min(1)).min(1) }).strict()
9624
10514
  };
9625
10515
  var coordinationDescriptions = {
9626
10516
  list_members: "\u67E5\u8BE2\u672C\u9879\u76EE\u53EF\u8054\u7CFB\u7684\u6210\u5458 ID\u3002",
9627
10517
  read_reference: "\u8BFB\u53D6\u77ED\u5F15\u7528\u3002\u4EFB\u52A1\u548C\u4F1A\u8BDD\u9ED8\u8BA4\u4F7F\u7528\u672C\u6B21\u4E8B\u4EF6\u6240\u4F9D\u636E\u7684\u7248\u672C\uFF0Clatest=true \u67E5\u8BE2\u6700\u65B0\u5185\u5BB9\u3002\u4F1A\u8BDD\u9ED8\u8BA4\u8BFB\u53D6\u6B63\u6587\uFF0Coverview/handoff \u8BFB\u53D6\u5DF2\u6709\u6574\u7406\u3002PR \u7684 details/comments/diff \u8BFB\u53D6\u8FDC\u7AEF\u5F53\u524D\u5185\u5BB9\uFF0C\u53EF\u80FD\u665A\u4E8E\u539F\u4E8B\u4EF6\u3002",
9628
10518
  query_matters: "\u67E5\u8BE2\u4E8B\u9879\u6807\u9898\u3001\u72B6\u6001\u3001\u5FC5\u8981\u7ED3\u8BBA\u548C\u53C2\u4E0E\u4EBA\uFF1B\u5148\u68C0\u67E5\u76F8\u5173\u4E8B\u9879\u662F\u5426\u5DF2\u5B58\u5728\uFF0C\u907F\u514D\u91CD\u590D\u521B\u5EFA\u3002",
9629
- read_matter: "\u6309\u4E8B\u9879\u5F15\u7528\u8BFB\u53D6\u5171\u4EAB\u4F1A\u8BDD\u4E0E\u6700\u8FD1\u7ED3\u8BBA\u3002\u4E0D\u8BFB\u53D6\u539F\u59CB\u79C1\u4EBA instant \u804A\u5929\u3002",
9630
- delegate_matter: "\u65B0\u5EFA\u6216\u7EE7\u7EED\u4E8B\u9879\u3002\u65B0\u5EFA\u586B title\uFF1B\u7EE7\u7EED\u586B ref\u3002text \u662F\u53EF\u5206\u4EAB\u7684\u76EE\u6807\u3001\u5FC5\u8981\u80CC\u666F\u548C\u660E\u786E\u6388\u6743\u8FB9\u754C\uFF0C\u4E0D\u590D\u5236\u79C1\u4EBA\u804A\u5929\u3002participants \u662F\u9080\u8BF7\u7684\u6709\u6548\u6210\u5458 ID\uFF1Bsources \u586B\u5173\u8054\u7684\u8D44\u6599\u77ED\u5F15\u7528\u3002\u9879\u76EE\u5DF2\u8FDE\u63A5\u9891\u9053\u65F6\uFF0C\u6709\u53C2\u4E0E\u4EBA\u7684\u65B0\u4E8B\u9879\u9ED8\u8BA4\u5411\u9879\u76EE\u5171\u4EAB\u5E76\u53D1\u5E16\uFF1B\u79C1\u4EBA\u5185\u5BB9\u6307\u5B9A visibility=participants\uFF0C\u53EA\u5728\u7F51\u9875\u5BF9\u53C2\u4E0E\u4EBA\u5F00\u653E\u3002\u5DF2\u6709\u4E8B\u9879\u4E0D\u6539\u53D8\u5171\u4EAB\u8303\u56F4\u3002\u540E\u53F0\u72EC\u7ACB\u6267\u884C\uFF0C\u4E0D\u5728\u672C\u8F6E\u7B49\u5F85\u3002",
9631
- report_matter: "\u4E8B\u9879\u6709\u91CD\u8981\u7ED3\u8BBA\u3001\u963B\u585E\u6216\u5B8C\u6210\u65F6\uFF0C\u5411\u9879\u76EE\u534F\u8C03\u8005\u56DE\u62A5\u7B80\u77ED summary \u4E0E\u72B6\u6001\u3002\u6CA1\u6709\u91CD\u8981\u53D8\u5316\u65F6\u65E0\u9700\u8C03\u7528\uFF1B\u4E0D\u590D\u5236\u8BA8\u8BBA\u8FC7\u7A0B\u548C\u5DE5\u5177\u8BB0\u5F55\u3002completed \u8868\u793A\u4E8B\u9879\u5B8C\u6210\uFF0C\u4E0D\u53EA\u662F\u672C\u8F6E\u56DE\u7B54\u7ED3\u675F\u3002",
10519
+ read_matter: "\u9ED8\u8BA4\u8BFB\u53D6\u4E8B\u9879\u76EE\u6807\u3001\u6700\u65B0\u7ED3\u8BBA\u3001\u5F85\u786E\u8BA4\u95EE\u9898\u53CA\u6682\u505C\u7EA6\u675F\u3002section=messages/reports \u6309 after \u6E38\u6807\u8BFB\u53D6\u8BE6\u7EC6\u8BB0\u5F55\uFF1B\u4E0D\u8BFB\u53D6\u539F\u59CB\u79C1\u4EBA\u804A\u5929\u3002",
10520
+ delegate_matter: "\u65B0\u5EFA\u6216\u7EE7\u7EED\u4E8B\u9879\u3002\u65B0\u5EFA\u586B title\uFF1B\u7EE7\u7EED\u586B ref\u3002text \u662F\u53EF\u5206\u4EAB\u7684\u76EE\u6807\u548C\u5FC5\u8981\u534F\u8C03\u80CC\u666F\uFF0C\u4FDD\u7559\u7528\u6237\u539F\u610F\uFF1B\u5BBF\u4E3B\u9644\u52A0\u539F\u59CB\u4E8B\u4EF6\u3002sources \u4EC5\u4F5C\u53C2\u8003\uFF0Cfollows \u8868\u793A\u660E\u786E\u8D1F\u8D23\u5176\u540E\u7EED\u4E8B\u4EF6\u3002participants \u586B\u9700\u8981\u9080\u8BF7\u7684\u6210\u5458\u3002\u79C1\u4EBA\u5185\u5BB9\u6307\u5B9A visibility=participants\u3002\u4E00\u6B21\u8F93\u5165\u91CD\u590D\u6D3E\u53D1\u8FD4\u56DE\u539F\u7ED3\u679C\uFF1B\u660E\u786E\u62C6\u6210\u72EC\u7ACB\u4E8B\u9879\u65F6\u4E3A\u6BCF\u4EF6\u5DE5\u4F5C\u586B\u5199\u7A33\u5B9A\u7684 branch\u3002\u6D3E\u53D1\u540E\u7ED3\u675F\u672C\u8F6E\u3002",
10521
+ report_matter: "\u4FDD\u5B58\u4E8B\u9879\u72B6\u6001\u548C\u7B80\u77ED summary\u3002kind=progress \u4EC5\u66F4\u65B0\u8BB0\u5F55\uFF0C\u4E0D\u5524\u9192\u534F\u8C03\u8005\uFF1Bkind=coordination \u9700\u5177\u4F53 request\uFF1Bkind=result \u7528\u4E8E\u91CD\u8981\u7ED3\u8BBA\u6216\u5B8C\u6210\uFF0C\u968F\u672C\u8F6E\u6210\u529F\u63D0\u4EA4\u3002pending \u8BB0\u5F55\u9700\u8981\u8C01\u786E\u8BA4\u4EC0\u4E48\uFF0Cnull \u8868\u793A\u95EE\u9898\u5DF2\u89E3\u51B3\uFF1B\u7701\u7565\u4FDD\u6301\u539F\u503C\u3002\u62A5\u544A\u4E0D\u66FF\u4EE3\u5BF9\u7528\u6237\u7684\u56DE\u7B54\u3002",
10522
+ handback_input: "\u5F53\u524D\u8F93\u5165\u5C5E\u4E8E\u53E6\u4E00\u4EF6\u5DE5\u4F5C\u65F6\uFF0C\u4EA4\u8FD8\u539F\u8F93\u5165\u4E0E\u7B80\u77ED\u539F\u56E0\u7ED9 lifetime\uFF1B\u4E0D\u91CD\u65B0\u6284\u5199\u6D88\u606F\uFF0C\u4E0D\u505C\u6B62\u6574\u4E2A\u4E8B\u9879\u3002\u9000\u56DE\u540E\u4E0D\u8981\u518D\u6267\u884C\u8FD9\u6761\u8BF7\u6C42\u3002",
10523
+ set_matter_hold: "\u4FDD\u5B58\u5F53\u524D\u7528\u6237\u660E\u786E\u63D0\u51FA\u7684\u6682\u505C\u8303\u56F4\uFF1Ascope=all \u6682\u505C\u6574\u4E2A\u4E8B\u9879\u7684\u81EA\u52A8\u5904\u7406\uFF0Cwrites \u6682\u7F13\u4E1A\u52A1\u5199\u5165\u3002\u660E\u786E\u6062\u590D\u65F6 hold=null\uFF1B\u666E\u901A\u8865\u5145\u3001\u8D44\u6599\u5F15\u7528\u548C\u81EA\u52A8\u4E8B\u4EF6\u4E0D\u6388\u6743\u89E3\u9664\u3002",
9632
10524
  notify_members: "\u672C\u8F6E\u786E\u5B9E\u9700\u8981\u6307\u5B9A\u53C2\u4E0E\u4EBA\u8865\u5145\u3001\u786E\u8BA4\u6216\u91C7\u53D6\u884C\u52A8\u65F6\uFF0C\u6807\u8BB0\u9700\u8981\u63D0\u9192\u7684\u6210\u5458\u3002\u53EA\u586B Teammy \u6210\u5458 ID\uFF1B\u5BBF\u4E3B\u5728\u672C\u8F6E\u56DE\u7B54\u53D1\u51FA\u65F6\u6620\u5C04\u6210\u4F01\u4E1A\u5FAE\u4FE1 @\u3002\u4E0D\u5355\u72EC\u53D1\u9001\u6D88\u606F\uFF0C\u4E0D\u8981\u6BCF\u8F6E\u8C03\u7528\uFF0C\u4E0D\u8981\u67E5\u627E\u6216\u8F93\u51FA\u4F01\u5FAE ID\u3002"
9633
10525
  };
9634
10526
  async function executeCoordinationTool(hub, run, name, input, signal, operation) {
@@ -9641,6 +10533,9 @@ async function executeCoordinationTool(hub, run, name, input, signal, operation)
9641
10533
  return matters.delegate(run, DelegateMatterSchema.parse(input), operation);
9642
10534
  if (name === "report_matter")
9643
10535
  return matters.report(run, ReportMatterSchema.parse(input), operation);
10536
+ if (name === "handback_input")
10537
+ return matters.handback(run, coordinationSchemas.handback_input.parse(input).reason);
10538
+ if (name === "set_matter_hold") return matters.hold(run, MatterHoldSchema.parse(input));
9644
10539
  if (name === "notify_members") {
9645
10540
  const { members } = coordinationSchemas.notify_members.parse(input);
9646
10541
  const valid = new Set(
@@ -9666,9 +10561,9 @@ async function executeCoordinationTool(hub, run, name, input, signal, operation)
9666
10561
  }
9667
10562
  const allowedMatter = (id) => run.role === "lifetime" || run.context_kind === "matter" && run.conversation_id === id || !run.context_kind && (matters.members(id).includes(run.owner) || matters.get(run.project_id, id).visibility === "project");
9668
10563
  if (name === "query_matters") {
9669
- const { status } = coordinationSchemas.query_matters.parse(input);
9670
- return hub.db.prepare("SELECT * FROM agent_matters WHERE project_id=? ORDER BY updated_at DESC").all(run.project_id).filter(
9671
- (row) => (!status || row.status === status) && allowedMatter(String(row.conversation_id))
10564
+ const { status, query: query2, member, source, offset, limit } = coordinationSchemas.query_matters.parse(input);
10565
+ const items = hub.db.prepare("SELECT * FROM agent_matters WHERE project_id=? ORDER BY updated_at DESC").all(run.project_id).filter(
10566
+ (row) => (!status || row.status === status) && allowedMatter(String(row.conversation_id)) && (!query2 || `${row.title} ${row.goal} ${row.summary}`.toLowerCase().includes(query2.toLowerCase())) && (!member || matters.members(String(row.conversation_id)).includes(member)) && (!source || !!hub.db.prepare("SELECT 1 FROM agent_matter_sources WHERE conversation_id=? AND ref=?").get(row.conversation_id, source))
9672
10567
  ).map((row) => ({
9673
10568
  ref: row.ref,
9674
10569
  title: row.title,
@@ -9676,6 +10571,10 @@ async function executeCoordinationTool(hub, run, name, input, signal, operation)
9676
10571
  summary: row.summary,
9677
10572
  participants: matters.members(String(row.conversation_id))
9678
10573
  }));
10574
+ return {
10575
+ items: items.slice(offset, offset + limit),
10576
+ nextOffset: offset + limit < items.length ? offset + limit : null
10577
+ };
9679
10578
  }
9680
10579
  const readMatter = (ref) => {
9681
10580
  const row = matters.get(run.project_id, ref);
@@ -9686,20 +10585,33 @@ async function executeCoordinationTool(hub, run, name, input, signal, operation)
9686
10585
  title: row.title,
9687
10586
  status: row.status,
9688
10587
  summary: row.summary,
9689
- participants: matters.members(String(row.conversation_id)),
9690
- messages: store.chatView(String(row.conversation_id)).runs.map((item) => ({
9691
- author: item.author ?? "\u63D0\u767E\u4E8B",
9692
- text: item.text,
9693
- answer: item.answer,
9694
- state: item.state
9695
- }))
10588
+ goal: row.goal,
10589
+ pending: row.pending_json ? JSON.parse(String(row.pending_json)) : null,
10590
+ hold: row.hold_json ? JSON.parse(String(row.hold_json)) : null,
10591
+ sources: hub.db.prepare(
10592
+ "SELECT ref,relation FROM agent_matter_sources WHERE conversation_id=? ORDER BY ref"
10593
+ ).all(row.conversation_id),
10594
+ updatedAt: row.updated_at,
10595
+ participants: matters.members(String(row.conversation_id))
9696
10596
  };
9697
10597
  };
9698
- if (name === "read_matter") return readMatter(coordinationSchemas.read_matter.parse(input).ref);
10598
+ if (name === "read_matter") {
10599
+ const value2 = coordinationSchemas.read_matter.parse(input), overview = readMatter(value2.ref), row = matters.get(run.project_id, value2.ref);
10600
+ if (value2.section === "overview") return overview;
10601
+ const items = value2.section === "messages" ? new AgentMessages(hub).list(String(row.conversation_id), value2.after, value2.limit + 1) : hub.db.prepare(
10602
+ "SELECT rowid seq,kind,status,summary,details_json,created_at FROM agent_matter_reports WHERE conversation_id=? AND state='committed' AND rowid>? ORDER BY rowid LIMIT ?"
10603
+ ).all(row.conversation_id, value2.after, value2.limit + 1);
10604
+ const page = items.slice(0, value2.limit);
10605
+ return {
10606
+ ...overview,
10607
+ items: page,
10608
+ next: items.length > value2.limit ? Number(page.at(-1).seq) : null
10609
+ };
10610
+ }
9699
10611
  const value = coordinationSchemas.read_reference.parse(input);
9700
10612
  const detailsRow = hub.db.prepare("SELECT details_json FROM agent_run_details WHERE run_id=?").get(run.id);
9701
- const details = detailsRow ? JSON.parse(String(detailsRow.details_json)) : {};
9702
- const target = !value.latest && details.references?.[value.ref] || resolveReference(hub, run.project_id, value.ref, value.latest ? null : run.task_id);
10613
+ const details2 = detailsRow ? JSON.parse(String(detailsRow.details_json)) : {};
10614
+ const target = !value.latest && details2.references?.[value.ref] || resolveReference(hub, run.project_id, value.ref, value.latest ? null : run.task_id);
9703
10615
  const query = (tool, args) => executeProjectQuery(hub, runActor(run), tool, args, signal, run.role === "lifetime");
9704
10616
  if (target.kind === "matter") return readMatter(target.id);
9705
10617
  if (target.kind === "task") {
@@ -9753,7 +10665,7 @@ async function executeCoordinationTool(hub, run, name, input, signal, operation)
9753
10665
  }
9754
10666
 
9755
10667
  // apps/server/materials/agent.ts
9756
- import { createHash as createHash16 } from "node:crypto";
10668
+ import { createHash as createHash17 } from "node:crypto";
9757
10669
  import { z as z32 } from "zod";
9758
10670
  var SaveMaterialSchema = z32.object({
9759
10671
  id: MaterialIdSchema.optional(),
@@ -9772,7 +10684,7 @@ var transport = SaveMaterialSchema.extend({
9772
10684
  _files: z32.array(z32.object({ name: z32.string(), base64: z32.string() }).strict()).optional()
9773
10685
  });
9774
10686
  function operationUuid(value) {
9775
- const bytes = createHash16("sha256").update(JSON.stringify(value)).digest().subarray(0, 16);
10687
+ const bytes = createHash17("sha256").update(JSON.stringify(value)).digest().subarray(0, 16);
9776
10688
  bytes[6] = bytes[6] & 15 | 64;
9777
10689
  bytes[8] = bytes[8] & 63 | 128;
9778
10690
  const hex = bytes.toString("hex");
@@ -9854,26 +10766,31 @@ var schemas2 = {
9854
10766
  reply_message: z33.object({ messageId: z33.uuid(), body: z33.string().trim().min(1) }).strict(),
9855
10767
  write_tasks: TaskWriteSchema,
9856
10768
  save_material: SaveMaterialSchema,
9857
- send_inbox: AgentTextSchema,
10769
+ send_inbox: AgentTextSchema.extend({ branch: z33.string().trim().min(1).optional() }),
9858
10770
  send_outbox: OutboxSendSchema,
9859
- post_pr_comment: z33.object({ number: z33.number().int().positive(), body: z33.string().trim().min(1) }).strict()
10771
+ post_pr_comment: z33.object({
10772
+ number: z33.number().int().positive(),
10773
+ body: z33.string().trim().min(1),
10774
+ branch: z33.string().trim().min(1).optional()
10775
+ }).strict()
9860
10776
  };
9861
10777
  var descriptions2 = {
9862
10778
  ...coordinationDescriptions,
9863
10779
  ...queryDescriptions,
9864
10780
  ...personalQueryDescriptions,
9865
- reply_message: "\u6839\u636E\u5F53\u524D\u7528\u6237\u660E\u786E\u63D0\u4F9B\u7684\u7B54\u590D\uFF0C\u56DE\u590D\u5176\u6536\u5230\u7684\u63D0\u767E\u4E8B\u6D88\u606F\u3002\u5148 read_message \u6838\u5BF9\u539F\u95EE\u9898\uFF1B\u6BCF\u6761\u6D88\u606F\u53EA\u80FD\u56DE\u590D\u4E00\u6B21\uFF0C\u53D1\u9001\u540E\u4EA4\u7531\u540E\u53F0\u7EE7\u7EED\u5904\u7406\u3002\u4E0D\u8981\u81EA\u884C\u731C\u6D4B\u7528\u6237\u7684\u51B3\u5B9A\u3002",
10781
+ reply_message: "\u6839\u636E\u5F53\u524D\u7528\u6237\u660E\u786E\u63D0\u4F9B\u7684\u7B54\u590D\uFF0C\u56DE\u590D\u5176\u6536\u5230\u7684Tim\u6D88\u606F\u3002\u5148 read_message \u6838\u5BF9\u539F\u95EE\u9898\uFF1B\u652F\u6301\u6301\u7EED\u591A\u8F6E\u56DE\u590D\u3002\u4E0D\u8981\u81EA\u884C\u731C\u6D4B\u7528\u6237\u7684\u51B3\u5B9A\u3002",
9866
10782
  write_tasks: "\u7528\u6237\u660E\u786E\u786E\u8BA4\u5177\u4F53\u8C03\u6574\u540E\uFF0C\u65B0\u589E\u6216\u4FEE\u6539\u672C\u9879\u76EE\u4EFB\u52A1\u3002\u5148\u7528 query_tasks \u67E5\u8BE2\u5F53\u524D\u4EFB\u52A1\uFF0C\u66F4\u65B0\u586B\u5199 expectedRevision\u3002materials \u586B\u5DF2\u6536\u5F55\u8D44\u6599\u7F16\u53F7\u53CA\u7248\u672C\u4F5C\u4E3A\u4F9D\u636E\uFF1B\u4E0D\u4FEE\u6539\u5DE5\u4F5C\u533A\u6587\u4EF6\u3002needsConfirmation \u53EA\u8868\u793A\u4EFB\u52A1\u8868\u8FF0\u5B58\u5728\u5F71\u54CD\u7406\u89E3\u7684\u6B67\u4E49\uFF0C\u4E0D\u8868\u793A\u7B49\u5F85\u63A5\u5355\u6216\u9A8C\u6536\u3002\u666E\u901A\u8BA8\u8BBA\u6216\u6536\u5F55\u8D44\u6599\u4E0D\u6388\u6743\u5199\u4EFB\u52A1\u3002",
9867
10783
  save_material: "\u6309\u7528\u6237\u6536\u5F55\u610F\u56FE\u4FDD\u5B58\u9879\u76EE\u8D44\u6599\uFF0C\u6216\u6574\u7406\u6307\u5B9A\u7684\u5DF2\u6709\u8D44\u6599\u3002\u65B0\u8D44\u6599\u63D0\u4F9B text\u3001url \u6216 originals \u6587\u4EF6\u8DEF\u5F84\uFF1B\u957F\u6B63\u6587\u7528 contentFile \u6307\u5411\u672C\u5730 Markdown\uFF0C\u539F\u4EF6\u7531\u5DE5\u5177\u76F4\u63A5\u4F20\u8F93\u3002\u4FEE\u6539\u5DF2\u6709\u8D44\u6599\u9700 id \u548C expectedRevision\u3002\u8FD4\u56DE\u7F16\u53F7\u53CA\u7F51\u9875\u94FE\u63A5\uFF1B\u4E0D\u4F1A\u4FEE\u6539\u4EFB\u52A1\u3002",
9868
- send_inbox: "\u5C06\u7528\u6237\u660E\u786E\u59D4\u6258\u7684\u4E8B\u9879\u4EA4\u7ED9 lifetime\u3002text \u5199\u6E05\u76EE\u6807\u3001\u5FC5\u8981\u80CC\u666F\u3001\u6765\u6E90\u548C\u671F\u671B\u7ED3\u679C\uFF1B\u9700\u8981\u53D1\u5E03 PR \u5BA1\u6838\u610F\u89C1\u65F6\u5199\u660E PR \u548C\u53D1\u5E03\u8981\u6C42\u3002\u5BBF\u4E3B\u8865\u5145\u53D1\u8D77\u4EBA\uFF0C\u8FD4\u56DE taskId \u4EC5\u8868\u793A\u5DF2\u5165\u961F\uFF0C\u7ED3\u679C\u901A\u8FC7 outbox \u53CD\u9988\u3002",
9869
- send_outbox: "\u5411\u9879\u76EE\u6210\u5458\u53D1\u9001\u544A\u77E5\u6216\u8BE2\u95EE\u3002to \u586B\u5E73\u53F0\u7528\u6237\u540D\u6570\u7EC4\uFF0C\u6216 debug \u8054\u7CFB\u8BCA\u65AD\u6536\u4EF6\u4EBA\u3002body \u5199\u6E05\u80CC\u666F\u3001\u5177\u4F53\u4E8B\u9879\u548C\u6765\u6E90\uFF0C\u8BA9\u6536\u4EF6\u4EBA\u65E0\u9700\u9605\u8BFB\u539F\u804A\u5929\u4E5F\u80FD\u7406\u89E3\u3002\u8FD4\u56DE messageId \u8868\u793A\u5DF2\u53D1\u9001\uFF1B\u56DE\u590D\u4EA4\u7ED9 lifetime \u7EE7\u7EED\u5904\u7406\uFF0C\u4EE3\u95EE\u7ED3\u679C\u901A\u8FC7 outbox \u53CD\u9988\u53D1\u8D77\u4EBA\u3002instant \u4EC5\u5728\u7528\u6237\u660E\u786E\u8981\u6C42\u8054\u7CFB\u65F6\u4F7F\u7528\u3002",
9870
- post_pr_comment: "\u5C06\u9879\u76EE\u7EA7\u5BA1\u6838\u610F\u89C1\u53D1\u5E03\u4E3A PR \u666E\u901A\u8BC4\u8BBA\u3002\u9700\u8981\u5B9A\u5411\u6C9F\u901A\u65F6\u4F7F\u7528 outbox\uFF0C\u4E0D\u4FEE\u6539 PR \u8BC4\u5BA1\u6216\u5408\u5E76\u72B6\u6001\u3002"
10784
+ send_inbox: "\u5C06\u7528\u6237\u660E\u786E\u59D4\u6258\u7684\u5DE5\u4F5C\u4EA4\u7ED9\u9879\u76EE\u534F\u8C03\u8005\u3002text \u4FDD\u7559\u76EE\u6807\u3001\u5FC5\u8981\u80CC\u666F\u3001\u6765\u6E90\u548C\u7EA6\u675F\uFF1B\u9700\u8981\u53D1\u5E03 PR \u5BA1\u6838\u610F\u89C1\u65F6\u5199\u660E\u53D1\u5E03\u8981\u6C42\u3002\u8FD4\u56DE taskId \u8868\u793A\u5DF2\u5165\u961F\uFF0C\u540E\u7EED\u53EF\u8FDB\u5165\u4E8B\u9879\u6301\u7EED\u6C9F\u901A\uFF1B\u4E0D\u8981\u627F\u8BFA\u5DF2\u5B8C\u6210\u3002\u72EC\u7ACB\u62C6\u5206\u591A\u4EF6\u5DE5\u4F5C\u65F6\u7528\u4E0D\u540C branch\uFF0C\u91CD\u8BD5\u6CBF\u7528\u539F branch\u3002",
10785
+ send_outbox: "\u5411\u9879\u76EE\u6210\u5458\u53D1\u9001\u7B80\u77ED\u901A\u77E5\u3002to \u586B\u6210\u5458 ID \u6570\u7EC4\uFF0Cdebug \u7528\u4E8E\u8BCA\u65AD\u3002\u6B63\u6587\u5199\u6E05\u5FC5\u8981\u80CC\u666F\u548C\u5177\u4F53\u95EE\u9898\u3002\u4E8B\u9879\u4E2D\u5199\u5165\u672C\u8F6E\u540C\u4E00\u4E2A\u56DE\u7B54\u4F4D\u7F6E\uFF0Cto \u53EA\u63D0\u9192\u5DF2\u6709\u53C2\u4E0E\u4EBA\uFF0C\u4E0D\u53E6\u5F00\u79C1\u804A\uFF1B\u8FD4\u56DE staged \u8868\u793A\u968F\u672C\u8F6E\u6210\u529F\u63D0\u4EA4\u3002\u5176\u4ED6\u573A\u666F\u7684 messageId \u8868\u793A\u5DF2\u5165\u6295\u9012\u961F\u5217\uFF0C\u56DE\u590D\u53EF\u4EE5\u591A\u8F6E\u7EE7\u7EED\u3002instant \u4EC5\u5728\u7528\u6237\u660E\u786E\u8981\u6C42\u8054\u7CFB\u65F6\u4F7F\u7528\uFF1B\u9700\u5411\u540C\u4E00\u53D7\u4F17\u53D1\u9001\u72EC\u7ACB\u901A\u77E5\u65F6\u586B\u4E0D\u540C branch\uFF0C\u91CD\u8BD5\u6CBF\u7528\u3002",
10786
+ post_pr_comment: "\u5C06\u9879\u76EE\u7EA7\u5BA1\u6838\u610F\u89C1\u53D1\u5E03\u4E3A PR \u666E\u901A\u8BC4\u8BBA\u3002\u9700\u8981\u5B9A\u5411\u6C9F\u901A\u65F6\u4F7F\u7528 outbox\uFF0C\u4E0D\u4FEE\u6539 PR \u8BC4\u5BA1\u6216\u5408\u5E76\u72B6\u6001\u3002\u540C\u4E00\u8F6E\u3001\u540C\u4E00 PR \u7684\u91CD\u8BD5\u590D\u7528\u539F\u8BC4\u8BBA\uFF1B\u786E\u9700\u53D1\u5E03\u72EC\u7ACB\u8BC4\u8BBA\u65F6\u6307\u5B9A\u4E0D\u540C branch\u3002"
9871
10787
  };
9872
10788
  function projectOutputOperation(run, name, input) {
9873
10789
  if (name === "delegate_matter" && run.role === "lifetime") {
9874
10790
  const value = schemas2.delegate_matter.parse(input);
9875
10791
  value.participants = [...new Set(value.participants)].sort();
9876
10792
  value.sources = [...new Set(value.sources)].sort();
10793
+ value.follows = [...new Set(value.follows)].sort();
9877
10794
  return { name, value, key: fingerprint([run.project_id, run.id, name, value]) };
9878
10795
  }
9879
10796
  if (name === "report_matter" && run.context_kind === "matter") {
@@ -9882,28 +10799,46 @@ function projectOutputOperation(run, name, input) {
9882
10799
  }
9883
10800
  if (name === "reply_message" && run.role === "instant" && !run.context_kind) {
9884
10801
  const value = schemas2.reply_message.parse(input);
9885
- return { name, value, key: fingerprint([run.project_id, run.id, name, value]) };
10802
+ return {
10803
+ name,
10804
+ value,
10805
+ key: fingerprint([run.project_id, run.id, name, value.messageId])
10806
+ };
9886
10807
  }
9887
10808
  if (name === "send_inbox" && run.role === "instant") {
9888
10809
  const value = schemas2.send_inbox.parse(input);
9889
- return { name, value, key: fingerprint([run.project_id, run.id, name, value]) };
10810
+ return {
10811
+ name,
10812
+ value,
10813
+ key: fingerprint([run.project_id, run.id, name, value.branch ?? ""])
10814
+ };
9890
10815
  }
9891
10816
  if (name === "send_outbox") {
9892
10817
  const value = schemas2.send_outbox.parse(input);
9893
10818
  if (Array.isArray(value.to)) value.to = [...new Set(value.to)].sort();
9894
- return { name, value, key: fingerprint([run.project_id, run.id, name, value]) };
10819
+ return {
10820
+ name,
10821
+ value,
10822
+ key: fingerprint([run.project_id, run.id, name, value.to, value.branch ?? ""])
10823
+ };
9895
10824
  }
9896
10825
  if (name === "post_pr_comment" && (run.role === "lifetime" || run.context_kind === "matter")) {
9897
10826
  const value = schemas2.post_pr_comment.parse(input);
9898
- return { name, value, key: fingerprint([run.project_id, run.id, name, value]) };
10827
+ return {
10828
+ name,
10829
+ value,
10830
+ key: fingerprint([run.project_id, run.id, name, value.number, value.branch ?? ""])
10831
+ };
9899
10832
  }
9900
10833
  return void 0;
9901
10834
  }
9902
10835
  function toolDefinitions(role, contextKind) {
9903
- return Object.keys(schemas2).filter((name) => name !== "delegate_matter" || role === "lifetime").filter((name) => name !== "report_matter" || contextKind === "matter").filter((name) => name !== "notify_members" || contextKind === "matter").filter((name) => contextKind !== "matter" || !["send_inbox", "send_outbox"].includes(name)).filter(
10836
+ return Object.keys(schemas2).filter((name) => name !== "delegate_matter" || role === "lifetime").filter((name) => name !== "report_matter" || contextKind === "matter").filter((name) => name !== "notify_members" || contextKind === "matter").filter(
10837
+ (name) => !["handback_input", "set_matter_hold"].includes(name) || contextKind === "matter"
10838
+ ).filter((name) => contextKind !== "matter" || name !== "send_inbox").filter(
9904
10839
  (name) => !["list_messages", "read_message", "reply_message"].includes(name) || role === "instant" && !contextKind
9905
10840
  ).filter(
9906
- (name) => contextKind === "material" ? name === "save_material" : role === "lifetime" ? !["send_inbox", "save_material", "write_tasks"].includes(name) : name !== "post_pr_comment" || contextKind === "matter"
10841
+ (name) => contextKind === "material" ? name === "save_material" : role === "lifetime" ? !["send_inbox", "save_material", "write_tasks", "post_pr_comment"].includes(name) : name !== "post_pr_comment" || contextKind === "matter"
9907
10842
  ).map((name) => ({
9908
10843
  name,
9909
10844
  label: name,
@@ -9918,6 +10853,10 @@ async function executeProjectTool(hub, run, name, input, signal) {
9918
10853
  throw new HubError(403, "AGENT_TOOL_UNAVAILABLE", "\u5F53\u524D Agent \u65E0\u6B64\u5DE5\u5177\u3002");
9919
10854
  const identity = runActor(run);
9920
10855
  if (run.context_kind) requireProjectActor(hub, identity);
10856
+ if (["write_tasks", "save_material", "post_pr_comment"].includes(name))
10857
+ new Matters(hub).assertWritable(run);
10858
+ if (run.context_kind === "matter" && !["handback_input", "read_matter", "query_matters"].includes(name) && hub.db.prepare("SELECT 1 FROM agent_input_handbacks WHERE run_id=?").get(run.id))
10859
+ throw new HubError(409, "MATTER_INPUT_RETURNED", "\u672C\u6761\u8F93\u5165\u5DF2\u9000\u56DE\u534F\u8C03\u8005\uFF0C\u4E0D\u518D\u6267\u884C\u5176\u64CD\u4F5C\u3002");
9921
10860
  if (Object.hasOwn(coordinationSchemas, name))
9922
10861
  return executeCoordinationTool(
9923
10862
  hub,
@@ -9947,7 +10886,7 @@ async function executeProjectTool(hub, run, name, input, signal) {
9947
10886
  const output = projectOutputOperation(run, name, input);
9948
10887
  if (output?.name === "reply_message") {
9949
10888
  requirePersonalActor(hub, identity);
9950
- return store.reply(identity, output.value.messageId, output.value.body);
10889
+ return store.reply(identity, output.value.messageId, output.value.body, output.key);
9951
10890
  }
9952
10891
  if (output?.name === "send_inbox") {
9953
10892
  return store.enqueue(
@@ -9956,12 +10895,38 @@ async function executeProjectTool(hub, run, name, input, signal) {
9956
10895
  ${output.value.text}` : delegationPrompt(run.owner, output.value.text),
9957
10896
  output.key,
9958
10897
  {
10898
+ origin: { kind: "human", ...run.owner ? { actor: run.owner } : {} },
9959
10899
  audit: { sourceRunId: run.id, requester: run.context_kind === "group" ? null : run.owner }
9960
10900
  }
9961
10901
  );
9962
10902
  }
9963
10903
  if (output?.name === "send_outbox") {
9964
10904
  const { value, key } = output;
10905
+ const notice = run.role === "lifetime" ? new Matters(hub).details(run).notification : void 0;
10906
+ if (notice && Array.isArray(value.to) && value.to.length === 1 && value.to[0] === notice.memberId) {
10907
+ hub.db.prepare(
10908
+ "INSERT INTO agent_answer_drafts VALUES(?,?) ON CONFLICT(run_id) DO UPDATE SET body=excluded.body"
10909
+ ).run(run.id, value.body);
10910
+ return {
10911
+ conversationId: notice.conversationId,
10912
+ status: "staged",
10913
+ note: "\u5DF2\u6682\u5B58\u5F53\u524D\u901A\u77E5\u7684\u7B54\u590D\uFF0C\u65E0\u9700\u518D\u6B21\u53D1\u9001\u3002"
10914
+ };
10915
+ }
10916
+ if (run.context_kind === "matter" && value.to !== "debug") {
10917
+ const members = new Matters(hub).members(run.conversation_id);
10918
+ if (value.to.some((member) => !members.includes(member)))
10919
+ throw new HubError(400, "MATTER_MEMBER_INVALID", "\u8BF7\u5148\u534F\u8C03\u9080\u8BF7\u9700\u8981\u53C2\u4E0E\u7684\u6210\u5458\u3002");
10920
+ hub.db.prepare(
10921
+ "INSERT INTO agent_answer_drafts VALUES(?,?) ON CONFLICT(run_id) DO UPDATE SET body=excluded.body"
10922
+ ).run(run.id, value.body);
10923
+ await executeCoordinationTool(hub, run, "notify_members", { members: value.to }, signal);
10924
+ return {
10925
+ conversationId: run.conversation_id,
10926
+ status: "staged",
10927
+ note: "\u5DF2\u6682\u5B58\uFF0C\u5C06\u968F\u672C\u8F6E\u6210\u529F\u63D0\u4EA4\uFF1B\u65E0\u9700\u91CD\u590D\u8F93\u51FA\u6B63\u6587\u3002"
10928
+ };
10929
+ }
9965
10930
  if (value.to === "debug") {
9966
10931
  const id = new Diagnostics(hub).record({
9967
10932
  project: run.project_id,
@@ -9984,9 +10949,10 @@ ${output.value.text}` : delegationPrompt(run.owner, output.value.text),
9984
10949
  }
9985
10950
  if (output?.name === "post_pr_comment") {
9986
10951
  const { value, key } = output;
10952
+ const prior = hub.db.prepare("SELECT body FROM project_agent_publications WHERE project_id=? AND operation_key=?").get(run.project_id, key);
9987
10953
  return gitProvider(hub, run.project_id).publish(
9988
10954
  value.number,
9989
- value.body,
10955
+ prior ? String(prior.body) : value.body,
9990
10956
  key,
9991
10957
  run.task_id,
9992
10958
  signal
@@ -10061,7 +11027,7 @@ var ProjectAgentRuntime = class {
10061
11027
  }
10062
11028
  hub;
10063
11029
  signal;
10064
- owner = randomUUID16();
11030
+ owner = randomUUID19();
10065
11031
  store;
10066
11032
  projects = /* @__PURE__ */ new Map();
10067
11033
  pending = /* @__PURE__ */ new Set();
@@ -10181,7 +11147,10 @@ var ProjectAgentRuntime = class {
10181
11147
  runId: run.id,
10182
11148
  cwd: run.context_kind === "matter" ? "/work" : "/project",
10183
11149
  attempt: run.attempts,
10184
- text: projectInputPrompt(run, project.workspace.repositoryError),
11150
+ text: [
11151
+ new Matters(this.hub).context(run),
11152
+ projectInputPrompt(run, project.workspace.repositoryError)
11153
+ ].filter(Boolean).join("\n\n"),
10185
11154
  sessionFile: run.session_file,
10186
11155
  sessionDir: `/state/${run.conversation_id}`,
10187
11156
  model,
@@ -10190,6 +11159,10 @@ var ProjectAgentRuntime = class {
10190
11159
  tools: toolDefinitions(run.role, run.context_kind)
10191
11160
  },
10192
11161
  async (message) => {
11162
+ if (message.type === "attempt_start") {
11163
+ if (this.store.active(run)) new Matters(this.hub).abandonDrafts(run.id);
11164
+ return;
11165
+ }
10193
11166
  if (message.type === "session") {
10194
11167
  this.store.sessionFile(run, message.file);
10195
11168
  return;
@@ -10198,6 +11171,10 @@ var ProjectAgentRuntime = class {
10198
11171
  this.store.delta(run, message.text);
10199
11172
  return;
10200
11173
  }
11174
+ if (message.type === "answer_reset") {
11175
+ this.store.resetAnswer(run);
11176
+ return;
11177
+ }
10201
11178
  if (message.type === "model_abort") {
10202
11179
  modelRequests.get(message.id)?.abort();
10203
11180
  return;
@@ -10235,7 +11212,9 @@ var ProjectAgentRuntime = class {
10235
11212
  if (output) outputErrors.delete(output.key);
10236
11213
  response = { value };
10237
11214
  } catch (error) {
10238
- if (output) outputErrors.set(output.key, { name: output.name, error });
11215
+ const rejectedProposal = output && ["delegate_matter", "report_matter"].includes(output.name) && error instanceof HubError && error.statusCode < 500;
11216
+ if (output && !rejectedProposal)
11217
+ outputErrors.set(output.key, { name: output.name, error });
10239
11218
  logs.record({
10240
11219
  project: run.project_id,
10241
11220
  code: "TOOL_FAILED",
@@ -10294,7 +11273,7 @@ var ProjectAgentRuntime = class {
10294
11273
  await project.boxes[conversation]?.close();
10295
11274
  delete project.boxes[conversation];
10296
11275
  delete project.repositories[conversation];
10297
- await rm5(join17(project.workspace.root, "contexts", conversation), {
11276
+ await rm6(join18(project.workspace.root, "contexts", conversation), {
10298
11277
  recursive: true,
10299
11278
  force: true
10300
11279
  });
@@ -10391,13 +11370,13 @@ async function runProjectAgents(hub, signal, once = false) {
10391
11370
  // apps/server/ai/sandbox-check.ts
10392
11371
  import { execFile } from "node:child_process";
10393
11372
  import { promisify } from "node:util";
10394
- import { mkdtemp as mkdtemp5, mkdir as mkdir7, rm as rm6 } from "node:fs/promises";
11373
+ import { mkdtemp as mkdtemp5, mkdir as mkdir7, rm as rm7 } from "node:fs/promises";
10395
11374
  import { tmpdir as tmpdir3 } from "node:os";
10396
- import { join as join18 } from "node:path";
11375
+ import { join as join19 } from "node:path";
10397
11376
  async function checkWorkerSandbox(hub) {
10398
- const root = await mkdtemp5(join18(tmpdir3(), "teammy-sandbox-check-"));
11377
+ const root = await mkdtemp5(join19(tmpdir3(), "teammy-sandbox-check-"));
10399
11378
  try {
10400
- await mkdir7(join18(root, "workspaces"));
11379
+ await mkdir7(join19(root, "workspaces"));
10401
11380
  const { command, args } = await sandboxCommand(root, false);
10402
11381
  args.splice(-1, 1, "-e", 'process.stdout.write("ready")');
10403
11382
  const result = await promisify(execFile)(command, args, {
@@ -10409,7 +11388,7 @@ async function checkWorkerSandbox(hub) {
10409
11388
  } catch {
10410
11389
  new RuntimeStatus(hub).recordSandbox("failed");
10411
11390
  } finally {
10412
- await rm6(root, { recursive: true, force: true });
11391
+ await rm7(root, { recursive: true, force: true });
10413
11392
  }
10414
11393
  }
10415
11394
 
@@ -10517,6 +11496,7 @@ async function runAiCycle(hub, stop, claimed) {
10517
11496
  }
10518
11497
  async function runAiWorker(directory, once = false) {
10519
11498
  const hub = new HubStore(directory), stop = new AbortController();
11499
+ new Maintenance(hub).assertReady();
10520
11500
  const shutdown = () => stop.abort();
10521
11501
  process.once("SIGINT", shutdown);
10522
11502
  process.once("SIGTERM", shutdown);
@@ -10742,17 +11722,17 @@ function registerAiRoutes(server, store) {
10742
11722
  }
10743
11723
 
10744
11724
  // apps/server/static.ts
10745
- import { join as join19 } from "node:path";
11725
+ import { join as join20 } from "node:path";
10746
11726
  import fastifyStatic from "@fastify/static";
10747
11727
  import { readFile as readFile4 } from "node:fs/promises";
10748
11728
  function registerWeb(server, root) {
10749
- const html = () => readFile4(join19(root, "index.html"), "utf8");
11729
+ const html = () => readFile4(join20(root, "index.html"), "utf8");
10750
11730
  server.get(
10751
11731
  "/downloads/teammy-skill.zip",
10752
- async (_request, reply) => reply.type("application/zip").header("cache-control", "no-cache").header("content-disposition", 'attachment; filename="teammy-skill.zip"').send(await readFile4(join19(root, "downloads/teammy-skill.zip")))
11732
+ async (_request, reply) => reply.type("application/zip").header("cache-control", "no-cache").header("content-disposition", 'attachment; filename="teammy-skill.zip"').send(await readFile4(join20(root, "downloads/teammy-skill.zip")))
10753
11733
  );
10754
11734
  server.register(fastifyStatic, {
10755
- root: join19(root, "assets"),
11735
+ root: join20(root, "assets"),
10756
11736
  prefix: "/assets/",
10757
11737
  immutable: true,
10758
11738
  maxAge: "1y",
@@ -10796,7 +11776,7 @@ function registerWeb(server, root) {
10796
11776
  }
10797
11777
 
10798
11778
  // apps/server/server.ts
10799
- import { randomUUID as randomUUID21 } from "node:crypto";
11779
+ import { randomUUID as randomUUID24 } from "node:crypto";
10800
11780
  import Fastify from "fastify";
10801
11781
 
10802
11782
  // packages/protocol/index.ts
@@ -11258,6 +12238,10 @@ function registerSessionRoutes(server, store) {
11258
12238
  const q = z43.object({ revision: z43.coerce.number().pipe(RevisionSchema).optional() }).parse(request.query);
11259
12239
  return sessions.read(authorizeRead(store, request), p.owner, p.key, q.revision);
11260
12240
  });
12241
+ server.get("/sessions/:owner/:key/baseline", guard, async (request) => {
12242
+ const p = z43.object({ owner: NameSchema, key: HashSchema }).parse(request.params);
12243
+ return sessions.baseline(authorize(request), p.owner, p.key);
12244
+ });
11261
12245
  server.get("/sessions/:owner/:key/analysis/:kind", async (request) => {
11262
12246
  const p = z43.object({ owner: NameSchema, key: HashSchema, kind: SessionAnalysisKindSchema }).parse(request.params);
11263
12247
  const q = z43.object({ revision: z43.coerce.number().pipe(RevisionSchema).optional() }).parse(request.query);
@@ -11385,21 +12369,21 @@ function registerFileRoutes(server, store) {
11385
12369
  }
11386
12370
 
11387
12371
  // apps/server/agents/routes.ts
11388
- import { createHash as createHash17 } from "node:crypto";
12372
+ import { createHash as createHash18 } from "node:crypto";
11389
12373
  import { z as z45 } from "zod";
11390
12374
 
11391
12375
  // apps/server/agents/ssh-key.ts
11392
12376
  import { execFile as execFile2 } from "node:child_process";
11393
12377
  import { promisify as promisify2 } from "node:util";
11394
- import { mkdtemp as mkdtemp6, writeFile as writeFile6, readFile as readFile5, rm as rm7 } from "node:fs/promises";
11395
- import { join as join20 } from "node:path";
12378
+ import { mkdtemp as mkdtemp6, writeFile as writeFile6, readFile as readFile5, rm as rm8 } from "node:fs/promises";
12379
+ import { join as join21 } from "node:path";
11396
12380
  import { tmpdir as tmpdir4 } from "node:os";
11397
12381
  async function projectSshKey(hub, project) {
11398
12382
  const config = new AgentConfig(hub), initial = config.secrets(project);
11399
12383
  if (initial.sshPublicKey) return config.view(project);
11400
- const directory = await mkdtemp6(join20(tmpdir4(), "teammy-ssh-key-"));
12384
+ const directory = await mkdtemp6(join21(tmpdir4(), "teammy-ssh-key-"));
11401
12385
  try {
11402
- const path = join20(directory, "key");
12386
+ const path = join21(directory, "key");
11403
12387
  let publicKey;
11404
12388
  if (initial.sshPrivateKey) {
11405
12389
  await writeFile6(path, initial.sshPrivateKey.replace(/\n?$/, "\n"), { mode: 384 });
@@ -11423,7 +12407,7 @@ async function projectSshKey(hub, project) {
11423
12407
  publicKey
11424
12408
  );
11425
12409
  } finally {
11426
- await rm7(directory, { recursive: true, force: true });
12410
+ await rm8(directory, { recursive: true, force: true });
11427
12411
  }
11428
12412
  }
11429
12413
 
@@ -11534,7 +12518,8 @@ ${text}`));
11534
12518
  if (id) store.chat(identity, id);
11535
12519
  new AiDefaults(hub).resolve(identity.workspaceId);
11536
12520
  config.initialize(identity.workspaceId);
11537
- return reply.code(202).send(store.ask(identity, AgentTextSchema.parse(req.body).text, id));
12521
+ const input = AgentTextSchema.parse(req.body);
12522
+ return reply.code(202).send(store.ask(identity, input.text, id, input.requestId));
11538
12523
  });
11539
12524
  server.get(`${path}/events`, async (req, reply) => {
11540
12525
  const identity = authorizeWeb(hub, req), snapshot = store.chat(identity, chatId(req.params));
@@ -11581,19 +12566,26 @@ data: ${event.data}
11581
12566
  webMutation(req);
11582
12567
  const identity = authorizeWeb(hub, req);
11583
12568
  const { cursor } = z45.object({ cursor: z45.number().int().nonnegative() }).strict().parse(req.body);
11584
- new Matters(hub).mark(identity, messageId(req.params), cursor);
12569
+ const id = messageId(req.params);
12570
+ store.ownedChat(identity, id);
12571
+ if (hub.db.prepare("SELECT 1 FROM agent_matters WHERE conversation_id=?").get(id))
12572
+ new Matters(hub).mark(identity, id, cursor);
12573
+ else {
12574
+ hub.db.prepare(
12575
+ `UPDATE agent_notice_threads SET read_seq=MAX(read_seq,MIN(?,COALESCE((SELECT MAX(seq) FROM agent_messages WHERE conversation_id=?),0))) WHERE conversation_id=? AND user_id=?`
12576
+ ).run(cursor, id, id, identity.memberId);
12577
+ const thread = hub.db.prepare(
12578
+ "SELECT message_id FROM agent_notice_threads WHERE conversation_id=? AND user_id=?"
12579
+ ).get(id, identity.memberId);
12580
+ if (thread) store.mark(identity, String(thread.message_id));
12581
+ }
11585
12582
  return { ok: true };
11586
12583
  });
11587
12584
  server.get("/agents/outbox", async (req) => store.messages(authorizeWeb(hub, req)));
11588
12585
  server.get("/agents/outbox/unread", async (req) => {
11589
12586
  const identity = authorizeWeb(hub, req);
11590
12587
  return {
11591
- unread: new Matters(hub).unread(identity) + Number(
11592
- hub.db.prepare(
11593
- `SELECT COUNT(*) n FROM project_agent_outbox o JOIN project_agent_recipients r ON r.message_id=o.id
11594
- WHERE o.project_id=? AND r.user_id=? AND r.read_at IS NULL AND r.archived_at IS NULL`
11595
- ).get(identity.workspaceId, identity.memberId).n
11596
- )
12588
+ unread: store.unread(identity)
11597
12589
  };
11598
12590
  });
11599
12591
  server.get(
@@ -11610,11 +12602,8 @@ data: ${event.data}
11610
12602
  });
11611
12603
  server.post("/agents/outbox/:id/reply", async (req) => {
11612
12604
  webMutation(req);
11613
- return store.reply(
11614
- authorizeWeb(hub, req),
11615
- messageId(req.params),
11616
- AgentReplySchema.parse(req.body).body
11617
- );
12605
+ const input = AgentReplySchema.parse(req.body);
12606
+ return store.reply(authorizeWeb(hub, req), messageId(req.params), input.body, input.requestId);
11618
12607
  });
11619
12608
  server.register(async (scope2) => {
11620
12609
  scope2.removeContentTypeParser("application/json");
@@ -11627,7 +12616,7 @@ data: ${event.data}
11627
12616
  const raw = req.body;
11628
12617
  const event = String(req.headers["x-github-event"] ?? "");
11629
12618
  const delivery = String(
11630
- req.headers["x-github-delivery"] ?? createHash17("sha256").update(raw).digest("hex")
12619
+ req.headers["x-github-delivery"] ?? createHash18("sha256").update(raw).digest("hex")
11631
12620
  );
11632
12621
  return new GitHub(hub, projectId(req)).receive(
11633
12622
  event,
@@ -11811,7 +12800,7 @@ function registerClientAccess(server, store) {
11811
12800
  import { z as z50 } from "zod";
11812
12801
 
11813
12802
  // apps/server/wecom/channels.ts
11814
- import { createHash as createHash18, randomUUID as randomUUID17 } from "node:crypto";
12803
+ import { createHash as createHash19, randomUUID as randomUUID20 } from "node:crypto";
11815
12804
  import { z as z48 } from "zod";
11816
12805
  var channelEventSchema = z48.object({
11817
12806
  msgid: z48.string().min(1),
@@ -11821,7 +12810,7 @@ var channelEventSchema = z48.object({
11821
12810
  from: z48.object({ userid: z48.string().optional(), aibotid: z48.string().optional() }),
11822
12811
  event: z48.object({ eventtype: z48.string() }).catchall(z48.unknown())
11823
12812
  });
11824
- var digest4 = (value) => createHash18("sha256").update(value).digest("hex");
12813
+ var digest4 = (value) => createHash19("sha256").update(value).digest("hex");
11825
12814
  var WecomChannels = class {
11826
12815
  constructor(hub) {
11827
12816
  this.hub = hub;
@@ -11894,7 +12883,7 @@ var WecomChannels = class {
11894
12883
  "UPDATE wecom_deliveries SET state='cancelled' WHERE bot_id=? AND project_id=? AND chat_type=3 AND state='pending'"
11895
12884
  ).run(bot, identity.workspaceId);
11896
12885
  if (topic)
11897
- this.hub.db.prepare("INSERT INTO wecom_channels VALUES(?,?,?,?,?)").run(randomUUID17(), bot, topic, identity.workspaceId, (/* @__PURE__ */ new Date()).toISOString());
12886
+ this.hub.db.prepare("INSERT INTO wecom_channels VALUES(?,?,?,?,?)").run(randomUUID20(), bot, topic, identity.workspaceId, (/* @__PURE__ */ new Date()).toISOString());
11898
12887
  });
11899
12888
  return this.list(identity);
11900
12889
  }
@@ -11935,7 +12924,7 @@ var WecomChannels = class {
11935
12924
  this.hub.db.prepare(
11936
12925
  `INSERT OR IGNORE INTO wecom_deliveries(id,bot_id,external_id,project_id,kind,source_id,body,created_at,chat_type,channel_json) VALUES(?,?,?,?,?,?,'',?,3,?)`
11937
12926
  ).run(
11938
- randomUUID17(),
12927
+ randomUUID20(),
11939
12928
  binding.bot_id,
11940
12929
  binding.topic_id,
11941
12930
  binding.project_id,
@@ -11980,19 +12969,21 @@ var WecomChannels = class {
11980
12969
  hasPost = true;
11981
12970
  };
11982
12971
  const runs = this.hub.db.prepare(
11983
- `SELECT r.*,a.member_id,w.run_id external_run,n.members_json FROM project_agent_runs r JOIN agent_run_actors a ON a.run_id=r.id LEFT JOIN agent_run_channels w ON w.run_id=r.id LEFT JOIN agent_run_notifications n ON n.run_id=r.id WHERE r.conversation_id=? ORDER BY r.rowid`
12972
+ `SELECT r.*,a.member_id,w.run_id external_run,n.members_json,m.body visible_answer,p.body visible_input FROM project_agent_runs r JOIN agent_run_actors a ON a.run_id=r.id LEFT JOIN agent_run_channels w ON w.run_id=r.id LEFT JOIN agent_run_notifications n ON n.run_id=r.id
12973
+ LEFT JOIN agent_messages m ON m.run_id=r.id AND m.role='assistant'
12974
+ LEFT JOIN agent_messages p ON p.run_id=r.id AND p.role='user' WHERE r.conversation_id=? ORDER BY r.rowid`
11984
12975
  ).all(conversationId);
11985
12976
  for (const [runIndex, run] of runs.entries()) {
11986
12977
  this.hub.db.prepare("INSERT OR IGNORE INTO wecom_matter_runs(run_id) VALUES(?)").run(run.id);
11987
12978
  const synced = this.hub.db.prepare("SELECT * FROM wecom_matter_runs WHERE run_id=?").get(run.id);
11988
12979
  if (!synced.input_collected && hasPost) {
11989
- if (run.member_id && !run.external_run)
12980
+ if (run.member_id && run.visible_input && !run.external_run)
11990
12981
  this.queue(binding, `web:${run.id}`, {
11991
12982
  conversationId,
11992
12983
  action: "reply",
11993
12984
  text: `**${run.member_id}**
11994
12985
 
11995
- ${run.text}`,
12986
+ ${run.visible_input}`,
11996
12987
  members: []
11997
12988
  });
11998
12989
  this.hub.db.prepare("UPDATE wecom_matter_runs SET input_collected=1 WHERE run_id=?").run(run.id);
@@ -12015,9 +13006,13 @@ ${run.text}`,
12015
13006
  }
12016
13007
  break;
12017
13008
  }
12018
- const body = run.state === "failed" ? `\u672C\u6B21\u5904\u7406\u672A\u5B8C\u6210\uFF0C\u53EF\u4EE5\u5728\u672C\u5E16\u7EE7\u7EED\u56DE\u590D\u6216\u5230\u7F51\u9875\u67E5\u770B\u3002${run.answer ? `
13009
+ if (run.state === "succeeded" && !run.visible_answer) {
13010
+ this.hub.db.prepare("UPDATE wecom_matter_runs SET answer_collected=1 WHERE run_id=?").run(run.id);
13011
+ continue;
13012
+ }
13013
+ const body = run.state === "failed" ? `\u672C\u6B21\u5904\u7406\u672A\u5B8C\u6210\uFF0C\u53EF\u4EE5\u5728\u672C\u5E16\u7EE7\u7EED\u56DE\u590D\u6216\u5230\u7F51\u9875\u67E5\u770B\u3002${run.visible_answer ? `
12019
13014
 
12020
- ${run.answer}` : ""}` : String(run.answer || "\u672C\u8F6E\u5904\u7406\u5DF2\u5B8C\u6210\u3002");
13015
+ ${run.visible_answer}` : ""}` : String(run.visible_answer);
12021
13016
  const members = run.members_json ? JSON.parse(String(run.members_json)) : [];
12022
13017
  const initialMembers = runIndex === 0 ? new Matters(this.hub).members(conversationId) : [];
12023
13018
  const recipients = [.../* @__PURE__ */ new Set([...initialMembers, ...members])];
@@ -12057,13 +13052,13 @@ ${run.answer}` : ""}` : String(run.answer || "\u672C\u8F6E\u5904\u7406\u5DF2\u5B
12057
13052
  };
12058
13053
 
12059
13054
  // apps/server/wecom/runtime.ts
12060
- import { randomUUID as randomUUID20 } from "node:crypto";
13055
+ import { randomUUID as randomUUID23 } from "node:crypto";
12061
13056
  import "@wecom/aibot-node-sdk";
12062
13057
 
12063
13058
  // apps/server/wecom/store.ts
12064
- import { createHash as createHash19, randomBytes as randomBytes8, randomUUID as randomUUID18 } from "node:crypto";
13059
+ import { createHash as createHash20, randomBytes as randomBytes8, randomUUID as randomUUID21 } from "node:crypto";
12065
13060
  import { z as z49 } from "zod";
12066
- var hash2 = (text) => createHash19("sha256").update(text).digest("hex");
13061
+ var hash2 = (text) => createHash20("sha256").update(text).digest("hex");
12067
13062
  var referencePattern = /\bTM-([A-F0-9]{12})\b/i;
12068
13063
  var incomingSchema = z49.object({
12069
13064
  msgid: z49.string().min(1),
@@ -12076,7 +13071,7 @@ var incomingSchema = z49.object({
12076
13071
  quote: z49.object({ text: z49.object({ content: z49.string() }).optional() }).passthrough().optional(),
12077
13072
  compat_info: z49.unknown().optional()
12078
13073
  }).passthrough();
12079
- var replyHint = "\u56DE\u5E94\u300C\u63D0\u767E\u4E8B\u300D\u7684\u95EE\u9898\u3002\u6CE8\u610F\uFF1A\u53EA\u80FD\u53D1\u9001\u4E00\u6761\u6D88\u606F\uFF0C\u300C\u63D0\u767E\u4E8B\u300D\u6536\u5230\u540E\u4F1A\u7EE7\u7EED\u5DE5\u4F5C\u3002\u5982\u679C\u6709\u65B0\u7684\u95EE\u9898\uFF0C\u300C\u63D0\u767E\u4E8B\u300D\u4F1A\u7EE7\u7EED\u53D1\u6D88\u606F\u6765\u95EE\u4F60\u3002";
13074
+ var replyHint = "\u53EF\u5F15\u7528\u672C\u6761\u6D88\u606F\u7EE7\u7EED\u56DE\u590D\u3002";
12080
13075
  var WecomStore = class {
12081
13076
  constructor(hub) {
12082
13077
  this.hub = hub;
@@ -12156,7 +13151,7 @@ var WecomStore = class {
12156
13151
  `INSERT OR IGNORE INTO wecom_deliveries
12157
13152
  (id,bot_id,external_id,user_id,project_id,kind,source_id,reference,body,created_at) VALUES(?,?,?,?,?,?,?,?,?,?)`
12158
13153
  ).run(
12159
- randomUUID18(),
13154
+ randomUUID21(),
12160
13155
  bot,
12161
13156
  external,
12162
13157
  identity?.memberId ?? null,
@@ -12274,8 +13269,19 @@ ${projects.map((p) => `${this.projectName(String(p.project_id))}\uFF08${p.projec
12274
13269
  notice("\u6B64\u9879\u76EE\u5DF2\u89E3\u9664\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\uFF0C\u8BF7\u5728\u7F51\u9875\u56DE\u590D\u539F\u6D88\u606F\u3002");
12275
13270
  return;
12276
13271
  }
12277
- this.agents.reply(identity2, String(delivery.source_id), text);
12278
- notice("\u56DE\u590D\u5DF2\u4FDD\u5B58\uFF0C\u7F51\u9875\u6D88\u606F\u5DF2\u540C\u6B65\u3002\u300C\u63D0\u767E\u4E8B\u300D\u4F1A\u7EE7\u7EED\u5904\u7406\u3002");
13272
+ const run2 = this.agents.reply(
13273
+ identity2,
13274
+ String(delivery.source_id),
13275
+ text,
13276
+ `wecom:${bot}:${msg.msgid}`
13277
+ );
13278
+ const target = this.hub.db.prepare("SELECT conversation_id FROM project_agent_runs WHERE id=?").get(run2.id);
13279
+ if (target && this.hub.db.prepare("SELECT 1 FROM wecom_matter_threads WHERE conversation_id=?").get(target.conversation_id))
13280
+ notice(
13281
+ `\u8FD9\u6761\u6D88\u606F\u5DF2\u8F6C\u5165\u4E8B\u9879\uFF0C\u8BF7\u5728\u4E8B\u9879\u4E2D\u7EE7\u7EED\u8BA8\u8BBA\uFF1A${this.url(identity2.workspaceId, `chat=${target.conversation_id}`)}`
13282
+ );
13283
+ else
13284
+ this.hub.db.prepare("INSERT OR IGNORE INTO wecom_requests VALUES(?,?,?,?,?)").run(run2.id, bot, msg.from.userid, user, identity2.workspaceId);
12279
13285
  return;
12280
13286
  }
12281
13287
  const identity = { memberId: user, workspaceId: String(binding.active_project) };
@@ -12304,7 +13310,7 @@ ${projects.map((p) => `${this.projectName(String(p.project_id))}\uFF08${p.projec
12304
13310
  ${msg.quote.text.content}
12305
13311
 
12306
13312
  ${text}` : text;
12307
- const run = this.agents.ask(identity, question, chat);
13313
+ const run = this.agents.ask(identity, question, chat, `wecom:${bot}:${msg.msgid}`);
12308
13314
  this.hub.db.prepare("INSERT INTO wecom_requests VALUES(?,?,?,?,?)").run(run.id, bot, msg.from.userid, user, identity.workspaceId);
12309
13315
  } catch (error) {
12310
13316
  if (!(error instanceof HubError)) throw error;
@@ -12328,7 +13334,7 @@ ${text}` : text;
12328
13334
  shortened += char;
12329
13335
  }
12330
13336
  if (shortened !== body) shortened += "\n\n\u2026\u2026\u5B8C\u6574\u5185\u5BB9\u8BF7\u5728\u7F51\u9875\u67E5\u770B\u3002";
12331
- return `**${this.projectName(project)} \xB7 \u63D0\u767E\u4E8B${reference3 ? " \xB7 " + reference3 : ""}**
13337
+ return `**${this.projectName(project)} \xB7 Tim${reference3 ? " \xB7 " + reference3 : ""}**
12332
13338
 
12333
13339
  ${shortened}
12334
13340
 
@@ -12375,13 +13381,14 @@ ${shortened}
12375
13381
  );
12376
13382
  }
12377
13383
  const answers = this.hub.db.prepare(
12378
- `SELECT r.*,w.external_id,w.user_id,w.project_id FROM wecom_requests w
12379
- JOIN project_agent_runs r ON r.id=w.run_id WHERE w.bot_id=? AND r.state IN ('succeeded','failed')
13384
+ `SELECT r.*,m.body visible_answer,w.external_id,w.user_id,w.project_id FROM wecom_requests w
13385
+ JOIN project_agent_runs r ON r.id=w.run_id JOIN agent_messages m ON m.run_id=r.id AND m.role='assistant'
13386
+ WHERE w.bot_id=? AND r.state IN ('succeeded','failed') AND (m.body<>'' OR r.state='failed')
12380
13387
  AND NOT EXISTS(SELECT 1 FROM wecom_deliveries d WHERE d.bot_id=w.bot_id AND d.external_id=w.external_id AND d.kind='answer' AND d.source_id=r.id)`
12381
13388
  ).all(bot);
12382
13389
  for (const row of answers) {
12383
13390
  const body = String(
12384
- row.answer || (row.state === "failed" ? "\u672C\u6B21\u5904\u7406\u672A\u5B8C\u6210\uFF0C\u8BF7\u5728\u7F51\u9875\u67E5\u770B\u8FD0\u884C\u60C5\u51B5\u3002" : "\u672C\u8F6E\u5904\u7406\u5DF2\u5B8C\u6210\u3002")
13391
+ row.visible_answer || (row.state === "failed" ? "\u672C\u6B21\u5904\u7406\u672A\u5B8C\u6210\uFF0C\u8BF7\u5728\u7F51\u9875\u67E5\u770B\u8FD0\u884C\u60C5\u51B5\u3002" : "\u672C\u8F6E\u5904\u7406\u5DF2\u5B8C\u6210\u3002")
12385
13392
  );
12386
13393
  this.queue(
12387
13394
  bot,
@@ -12418,7 +13425,7 @@ ${shortened}
12418
13425
 
12419
13426
  // apps/server/wecom/transport.ts
12420
13427
  import { EventEmitter } from "node:events";
12421
- import { randomUUID as randomUUID19 } from "node:crypto";
13428
+ import { randomUUID as randomUUID22 } from "node:crypto";
12422
13429
  import WebSocket from "ws";
12423
13430
  var WecomTransport = class extends EventEmitter {
12424
13431
  constructor(options, open4 = () => new WebSocket("wss://openws.work.weixin.qq.com", { handshakeTimeout: 15e3 })) {
@@ -12448,7 +13455,7 @@ var WecomTransport = class extends EventEmitter {
12448
13455
  socket.addEventListener("open", () => {
12449
13456
  if (this.socket !== socket || this.stopped) return;
12450
13457
  void this.reply(
12451
- { headers: { req_id: randomUUID19() } },
13458
+ { headers: { req_id: randomUUID22() } },
12452
13459
  { bot_id: this.options.botId, secret: this.options.secret },
12453
13460
  "aibot_subscribe"
12454
13461
  ).then((frame) => {
@@ -12457,7 +13464,7 @@ var WecomTransport = class extends EventEmitter {
12457
13464
  this.attempts = 0;
12458
13465
  this.emit("authenticated");
12459
13466
  this.heartbeat = setInterval(() => {
12460
- void this.reply({ headers: { req_id: randomUUID19() } }, {}, "ping").then((result) => {
13467
+ void this.reply({ headers: { req_id: randomUUID22() } }, {}, "ping").then((result) => {
12461
13468
  if (result.errcode !== 0) socket.close();
12462
13469
  }).catch(() => socket.close());
12463
13470
  }, 25e3);
@@ -12556,7 +13563,7 @@ var WecomTransport = class extends EventEmitter {
12556
13563
  };
12557
13564
 
12558
13565
  // apps/server/wecom/channel-runtime.ts
12559
- import { createHash as createHash20 } from "node:crypto";
13566
+ import { createHash as createHash21 } from "node:crypto";
12560
13567
  var channelRetryDelay = (attempts) => Math.round(
12561
13568
  Math.min(30 * 6e4, 5e3 * 2 ** Math.min(attempts, 9)) * (0.75 + Math.random() * 0.5)
12562
13569
  );
@@ -12650,7 +13657,7 @@ var WecomChannelRuntime = class {
12650
13657
  text(bot, project, item) {
12651
13658
  let text = item.content ?? "";
12652
13659
  for (const mention of item.inline_mention_list ?? []) {
12653
- const name = mention.member.type === 1 ? "\u63D0\u767E\u4E8B" : this.store.speaker(bot, project, mention.member.userid ?? "");
13660
+ const name = mention.member.type === 1 ? "Tim" : this.store.speaker(bot, project, mention.member.userid ?? "");
12654
13661
  text = text.split("${inline_mention:" + mention.placeholder + "}").join(mention.member.type === 1 ? "" : `@${name}`);
12655
13662
  }
12656
13663
  for (const file of item.inline_file_list ?? [])
@@ -12732,6 +13739,22 @@ var WecomChannelRuntime = class {
12732
13739
  ).get(bot, topic);
12733
13740
  if (!binding) return;
12734
13741
  const project = String(binding.project_id);
13742
+ if (this.hub.db.prepare("SELECT 1 FROM reset_channel_posts WHERE bot_id=? AND topic_id=? AND post_id=?").get(bot, topic, post)) {
13743
+ if (type === "topic_at_bot")
13744
+ this.store.queue(
13745
+ { id: String(binding.id), bot_id: bot, topic_id: topic, project_id: project },
13746
+ `retired:${post}`,
13747
+ {
13748
+ conversationId: "",
13749
+ action: "reply",
13750
+ postId: post,
13751
+ text: "\u8FD9\u6761\u6D4B\u8BD5\u4E8B\u9879\u5DF2\u6E05\u7406\u3002\u8BF7\u65B0\u5EFA\u5E16\u5B50\u5E76 @Tim \u5F00\u59CB\u8BA8\u8BBA\u3002",
13752
+ members: []
13753
+ },
13754
+ "channel_retired"
13755
+ );
13756
+ return;
13757
+ }
12735
13758
  const thread = this.hub.db.prepare(
12736
13759
  "SELECT * FROM wecom_matter_threads WHERE bot_id=? AND topic_id=? AND post_id=? AND binding_id=?"
12737
13760
  ).get(bot, topic, post, binding.id);
@@ -12754,7 +13777,7 @@ var WecomChannelRuntime = class {
12754
13777
  const external = creator?.userid || event.from.userid;
12755
13778
  const text = recalled ? "\u64A4\u56DE\u4E86\u6B64\u524D\u7684\u4E00\u6761\u53D1\u8A00\u3002\u8BF7\u636E\u6B64\u66F4\u65B0\u7406\u89E3\uFF1B\u5DF2\u6267\u884C\u7684\u64CD\u4F5C\u4E0D\u4F1A\u81EA\u52A8\u64A4\u9500\u3002" : this.text(bot, project, item);
12756
13779
  if (!text) return;
12757
- const hash3 = createHash20("sha256").update(JSON.stringify([recalled, text])).digest("hex");
13780
+ const hash3 = createHash21("sha256").update(JSON.stringify([recalled, text])).digest("hex");
12758
13781
  const modified = Number(item?.modify_time || item?.create_time || event.create_time || 0);
12759
13782
  const existing = this.hub.db.prepare(
12760
13783
  "SELECT * FROM wecom_channel_messages WHERE bot_id=? AND topic_id=? AND post_id=? AND item_id=?"
@@ -12787,12 +13810,12 @@ var WecomChannelRuntime = class {
12787
13810
  if (command) {
12788
13811
  this.store.queue(
12789
13812
  { id: String(binding.id), bot_id: bot, topic_id: topic, project_id: project },
12790
- `command:${createHash20("sha256").update(JSON.stringify([topic, post, itemId, hash3])).digest("hex")}`,
13813
+ `command:${createHash21("sha256").update(JSON.stringify([topic, post, itemId, hash3])).digest("hex")}`,
12791
13814
  {
12792
13815
  conversationId: conversation,
12793
13816
  action: "reply",
12794
13817
  postId: post,
12795
- text: "\u9891\u9053\u4E2D\u6BCF\u4E2A\u5E16\u5B50\u72EC\u7ACB\u8BA8\u8BBA\u3002\u65B0\u95EE\u9898\u8BF7\u65B0\u5EFA\u5E16\u5B50\u5E76 @\u63D0\u767E\u4E8B\uFF1B\u5DF2\u63A5\u5165\u7684\u4E8B\u9879\u53EF\u76F4\u63A5\u5728\u5E16\u4E0B\u56DE\u590D\u3002/help \u67E5\u770B\u8BF4\u660E\u3002/bind\u3001/projects\u3001/use \u7528\u4E8E\u79C1\u804A\u6216\u7FA4\u804A\uFF0C\u9891\u9053\u8FDE\u63A5\u8BF7\u5728\u9879\u76EE\u8BBE\u7F6E\u4E2D\u7BA1\u7406\u3002",
13818
+ text: "\u9891\u9053\u4E2D\u6BCF\u4E2A\u5E16\u5B50\u72EC\u7ACB\u8BA8\u8BBA\u3002\u65B0\u95EE\u9898\u8BF7\u65B0\u5EFA\u5E16\u5B50\u5E76 @Tim\uFF1B\u5DF2\u63A5\u5165\u7684\u4E8B\u9879\u53EF\u76F4\u63A5\u5728\u5E16\u4E0B\u56DE\u590D\u3002/help \u67E5\u770B\u8BF4\u660E\u3002/bind\u3001/projects\u3001/use \u7528\u4E8E\u79C1\u804A\u6216\u7FA4\u804A\uFF0C\u9891\u9053\u8FDE\u63A5\u8BF7\u5728\u9879\u76EE\u8BBE\u7F6E\u4E2D\u7BA1\u7406\u3002",
12796
13819
  members: []
12797
13820
  }
12798
13821
  );
@@ -12818,7 +13841,7 @@ ${this.text(bot, project, value)}`;
12818
13841
  conversation,
12819
13842
  input,
12820
13843
  member,
12821
- {},
13844
+ { routing: { inputKey: `channel:${bot}:${topic}:${post}:${itemId}:${hash3}` } },
12822
13845
  { bindingId: String(binding.id), externalId: external, speaker }
12823
13846
  );
12824
13847
  const save = (id, contentHash2, at) => this.hub.db.prepare(
@@ -12828,7 +13851,7 @@ ${this.text(bot, project, value)}`;
12828
13851
  const body = this.text(bot, project, value);
12829
13852
  save(
12830
13853
  String(value.reply_id || value.post_id),
12831
- createHash20("sha256").update(JSON.stringify([!!value.is_recalled, body])).digest("hex"),
13854
+ createHash21("sha256").update(JSON.stringify([!!value.is_recalled, body])).digest("hex"),
12832
13855
  Number(value.modify_time || value.create_time || 0)
12833
13856
  );
12834
13857
  }
@@ -12934,7 +13957,7 @@ var WecomRuntime = class {
12934
13957
  }
12935
13958
  hub;
12936
13959
  factory;
12937
- owner = randomUUID20();
13960
+ owner = randomUUID23();
12938
13961
  store;
12939
13962
  client;
12940
13963
  revision = 0;
@@ -13001,7 +14024,7 @@ var WecomRuntime = class {
13001
14024
  this.hub,
13002
14025
  async (cmd, body) => {
13003
14026
  if (!channelActive() || !this.own()) throw new Error("\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002");
13004
- const response = await client.reply({ headers: { req_id: randomUUID20() } }, body, cmd);
14027
+ const response = await client.reply({ headers: { req_id: randomUUID23() } }, body, cmd);
13005
14028
  if (!channelActive()) throw new Error("\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002");
13006
14029
  return response;
13007
14030
  },
@@ -13214,19 +14237,28 @@ function buildServer(options) {
13214
14237
  ]
13215
14238
  } : false,
13216
14239
  requestIdHeader: false,
13217
- genReqId: () => randomUUID21(),
14240
+ genReqId: () => randomUUID24(),
13218
14241
  bodyLimit: 1024 * 1024,
13219
14242
  requestTimeout: BLOB_TRANSFER_TIMEOUT_MS
13220
14243
  });
13221
14244
  server.decorate("teammyDeployment", deployment);
13222
14245
  if (store) {
14246
+ const stopApi = new Maintenance(store).apiLease();
13223
14247
  const stopLogs = maintainDiagnostics(store);
13224
14248
  server.addHook("onClose", async () => {
13225
14249
  stopLogs();
14250
+ stopApi();
13226
14251
  store.close();
13227
14252
  });
13228
14253
  }
13229
14254
  server.addHook("onRequest", async (request, reply) => {
14255
+ if (store) {
14256
+ new Maintenance(store).assertReady();
14257
+ reply.header(
14258
+ "x-teammy-data-generation",
14259
+ String(new Maintenance(store).baseline().generation)
14260
+ );
14261
+ }
13230
14262
  reply.header("x-request-id", request.id);
13231
14263
  if (request.url.startsWith("/api/")) reply.header("cache-control", "no-store");
13232
14264
  });
@@ -13238,7 +14270,7 @@ function buildServer(options) {
13238
14270
  });
13239
14271
  server.setErrorHandler((error, request, reply) => {
13240
14272
  const candidate = error.statusCode;
13241
- const status = error instanceof ZodError ? 400 : candidate && candidate >= 400 && candidate < 500 ? candidate : 500;
14273
+ const status = error instanceof HubError ? error.statusCode : error instanceof ZodError ? 400 : candidate && candidate >= 400 && candidate < 500 ? candidate : 500;
13242
14274
  if (status === 500)
13243
14275
  request.log.error(
13244
14276
  { code: "REQUEST_FAILED", requestId: request.id },
@@ -13288,6 +14320,7 @@ function buildServer(options) {
13288
14320
  "atomic-file-publications",
13289
14321
  "project-files",
13290
14322
  "session-revisions",
14323
+ "session-baselines",
13291
14324
  "session-search",
13292
14325
  "session-analysis",
13293
14326
  "session-history-search",
@@ -13337,12 +14370,13 @@ function buildServer(options) {
13337
14370
 
13338
14371
  // apps/server/main.ts
13339
14372
  import { homedir } from "node:os";
13340
- import { join as join21, resolve as resolve4 } from "node:path";
14373
+ import { join as join22, resolve as resolve4 } from "node:path";
13341
14374
  import { spawn as spawn4 } from "node:child_process";
13342
14375
  var help = `teammy-server \u2014 the Teammy collaboration hub
13343
14376
 
13344
14377
  Usage: teammy-server [--host HOST] [--port PORT]
13345
14378
  teammy-server ai-worker [--data-dir DIR] [--once]
14379
+ teammy-server reset-test-data --confirm [--data-dir DIR]
13346
14380
  teammy-server member <add|reset|revoke> --workspace ID --member ID [--data-dir DIR]
13347
14381
 
13348
14382
  Options:
@@ -13366,16 +14400,42 @@ Endpoints:
13366
14400
  Open the server URL to initialize the Web dashboard and preset member IDs.
13367
14401
  File synchronization, the conversation library (\u5BF9\u8BDD\u5E93) and automatic AI analysis are available.
13368
14402
  AI tasks require --with-worker or a separate ai-worker using the same data directory.
14403
+ reset-test-data removes test tasks, chats, materials and agent memory while retaining
14404
+ accounts, connections and workspace sync data. Stop API and Worker before running it.
13369
14405
  `;
13370
14406
  async function runServerCli(args, version2) {
13371
14407
  try {
14408
+ if (args[0] === "reset-test-data") {
14409
+ const { values: values2 } = parseArgs({
14410
+ args: args.slice(1),
14411
+ options: {
14412
+ confirm: { type: "boolean" },
14413
+ "data-dir": {
14414
+ type: "string",
14415
+ default: process.env.TEAMMY_DATA_DIR ?? join22(homedir(), ".teammy", "server")
14416
+ }
14417
+ }
14418
+ });
14419
+ if (!values2.confirm)
14420
+ throw new Error(
14421
+ "This removes all test business data. Stop API/Worker, then pass --confirm to proceed."
14422
+ );
14423
+ const hub = new HubStore(resolve4(values2["data-dir"]));
14424
+ try {
14425
+ process.stdout.write(`${JSON.stringify(await new Maintenance(hub).resetTestData())}
14426
+ `);
14427
+ } finally {
14428
+ hub.close();
14429
+ }
14430
+ return 0;
14431
+ }
13372
14432
  if (args[0] === "ai-worker") {
13373
14433
  const { values: values2 } = parseArgs({
13374
14434
  args: args.slice(1),
13375
14435
  options: {
13376
14436
  "data-dir": {
13377
14437
  type: "string",
13378
- default: process.env.TEAMMY_DATA_DIR ?? join21(homedir(), ".teammy", "server")
14438
+ default: process.env.TEAMMY_DATA_DIR ?? join22(homedir(), ".teammy", "server")
13379
14439
  },
13380
14440
  once: { type: "boolean" }
13381
14441
  }
@@ -13394,7 +14454,7 @@ async function runServerCli(args, version2) {
13394
14454
  member: { type: "string" },
13395
14455
  "data-dir": {
13396
14456
  type: "string",
13397
- default: process.env.TEAMMY_DATA_DIR ?? join21(homedir(), ".teammy", "server")
14457
+ default: process.env.TEAMMY_DATA_DIR ?? join22(homedir(), ".teammy", "server")
13398
14458
  }
13399
14459
  }
13400
14460
  });
@@ -13422,7 +14482,7 @@ async function runServerCli(args, version2) {
13422
14482
  port: { type: "string", default: process.env.TEAMMY_PORT ?? "4317" },
13423
14483
  "data-dir": {
13424
14484
  type: "string",
13425
- default: process.env.TEAMMY_DATA_DIR ?? join21(homedir(), ".teammy", "server")
14485
+ default: process.env.TEAMMY_DATA_DIR ?? join22(homedir(), ".teammy", "server")
13426
14486
  },
13427
14487
  "with-worker": { type: "boolean", default: false }
13428
14488
  }