@akagilnc/pi-workflow-roles 0.1.5289 → 0.1.5295

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.
@@ -1107,6 +1107,20 @@ function projectDiaristSessions(value) {
1107
1107
  if (raw === void 0) return [];
1108
1108
  return projectTicketProvenanceSessions(raw);
1109
1109
  }
1110
+ function projectDiaristTicketSessions(value) {
1111
+ const raw = value.ticketSessions;
1112
+ if (!Array.isArray(raw)) return void 0;
1113
+ const perTicket = /* @__PURE__ */ new Map();
1114
+ for (const entry of raw) {
1115
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return void 0;
1116
+ const { ticketNumber, sessions: rawSessions } = entry;
1117
+ if (!isSafePositiveTicketNumber(ticketNumber)) return void 0;
1118
+ const sessions = projectTicketProvenanceSessions(rawSessions);
1119
+ if (sessions === void 0) return void 0;
1120
+ perTicket.set(ticketNumber, [...perTicket.get(ticketNumber) ?? [], ...sessions]);
1121
+ }
1122
+ return Array.from(perTicket, ([ticketNumber, sessions]) => ({ ticketNumber, sessions }));
1123
+ }
1110
1124
  var DIARIST_OUTPUT_TOOL_NAME;
1111
1125
  var init_diarist_contracts = __esm({
1112
1126
  "src/diarist-contracts.ts"() {
@@ -4888,42 +4902,37 @@ async function projectSessionRanges(input) {
4888
4902
  }
4889
4903
  return { entries };
4890
4904
  }
4891
- async function reprojectTicketProvenance(input) {
4892
- const recordInput = ticketProvenanceRecordInput(
4893
- input.ticketNumber,
4894
- input.cwd,
4895
- input.home,
4896
- input.runDirectory
4897
- );
4898
- const resolved = resolveSitianRecordPath(recordInput);
4899
- if (input.sessions.length === 0) {
4900
- return {
4901
- recordFile: resolved.recordFile,
4902
- lines: []
4903
- };
4905
+ async function reprojectTicketProvenanceBatch(input) {
4906
+ const projections = [];
4907
+ for (const ticket of input.tickets) {
4908
+ const recordInput = ticketProvenanceRecordInput(
4909
+ ticket.ticketNumber,
4910
+ input.cwd,
4911
+ input.home,
4912
+ input.runDirectory
4913
+ );
4914
+ const entries = [];
4915
+ for (let s = 0; s < ticket.sessions.length; s += 1) {
4916
+ const projected = await projectSessionRanges({
4917
+ s,
4918
+ session: ticket.sessions[s],
4919
+ ...input.home === void 0 ? {} : { home: input.home }
4920
+ });
4921
+ entries.push(...projected.entries);
4922
+ }
4923
+ projections.push({ ticket, recordInput, entries });
4904
4924
  }
4905
- const entries = [];
4906
- for (let s = 0; s < input.sessions.length; s += 1) {
4907
- const session = input.sessions[s];
4908
- const projected = await projectSessionRanges({
4909
- s,
4910
- session,
4911
- ...input.home === void 0 ? {} : { home: input.home }
4925
+ for (const { ticket, recordInput, entries } of projections) {
4926
+ if (ticket.sessions.length === 0) continue;
4927
+ appendSitianRecord({
4928
+ ...recordInput,
4929
+ payload: {
4930
+ type: "ticket-provenance-append",
4931
+ sessions: ticket.sessions,
4932
+ lines: entries
4933
+ }
4912
4934
  });
4913
- entries.push(...projected.entries);
4914
4935
  }
4915
- const pointer = appendSitianRecord({
4916
- ...recordInput,
4917
- payload: {
4918
- type: "ticket-provenance-append",
4919
- sessions: input.sessions,
4920
- lines: entries
4921
- }
4922
- });
4923
- return {
4924
- recordFile: pointer.recordFile,
4925
- lines: entries.filter((entry) => "speaker" in entry)
4926
- };
4927
4936
  }
4928
4937
  var TicketProvenanceInputError;
4929
4938
  var init_ticket_provenance = __esm({
@@ -21877,6 +21886,37 @@ init_terminating_infrastructure();
21877
21886
  init_diarist_contracts();
21878
21887
  init_diarist_contracts();
21879
21888
  import { Type as Type16 } from "typebox";
21889
+ var diaristSessionsSchema = Type16.Array(
21890
+ Type16.Object(
21891
+ {
21892
+ path: Type16.Optional(Type16.String({ description: "\u4F1A\u8BDD\u5377\u7EDD\u5BF9\u6216\u53EF\u8BFB\u8DEF\u5F84" })),
21893
+ ranges: Type16.Optional(
21894
+ Type16.Array(
21895
+ Type16.Object(
21896
+ {
21897
+ from: Type16.Optional(
21898
+ Type16.Object(
21899
+ { id: Type16.Optional(Type16.String()), line: Type16.Optional(Type16.Unknown()) },
21900
+ { additionalProperties: true, description: "\u8D77\u70B9\uFF1A\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00" }
21901
+ )
21902
+ ),
21903
+ to: Type16.Optional(
21904
+ Type16.Object(
21905
+ { id: Type16.Optional(Type16.String()), line: Type16.Optional(Type16.Unknown()) },
21906
+ { additionalProperties: true, description: "\u7EC8\u70B9\uFF1A\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00" }
21907
+ )
21908
+ )
21909
+ },
21910
+ { additionalProperties: true, description: "\u4E00\u6BB5\u5BF9\u8BDD\u533A\u95F4" }
21911
+ ),
21912
+ { description: "\u672C\u5377\u672C\u8F6E\u5404\u533A\u95F4\uFF1B\u81F3\u5C11\u4E00\u6BB5" }
21913
+ )
21914
+ )
21915
+ },
21916
+ { additionalProperties: true, description: "\u4E00\u5377\u4F1A\u8BDD\u7684\u8FB9\u754C" }
21917
+ ),
21918
+ { description: "\u5BF9\u8BDD\u8FB9\u754C\uFF1B\u7A7A\u5217\u8868\uFF1D\u672C\u8F6E\u65E0\u5BF9\u8BDD\u53EF\u5212\u3002\u7AEF\u70B9\u65E0\u6CD5\u6307\u540D\u65F6\u8D70 reask\uFF0C\u4E0D\u4E2D\u6B62\u3002" }
21919
+ );
21880
21920
  var diaristOutputSchema = withTerminatingOutputDeclarations(
21881
21921
  openToolObject(
21882
21922
  Type16.Object({
@@ -21898,53 +21938,17 @@ var diaristOutputSchema = withTerminatingOutputDeclarations(
21898
21938
  description: "status \u4E3A escalate \u65F6\uFF1A\u8BA4\u4E0D\u51FA\u672C\u5EAD\u5BF9\u8C61\u7684\u539F\u56E0"
21899
21939
  })
21900
21940
  ),
21901
- sessions: Type16.Optional(
21941
+ sessions: Type16.Optional(diaristSessionsSchema),
21942
+ ticketSessions: Type16.Optional(
21902
21943
  Type16.Array(
21903
21944
  Type16.Object(
21904
21945
  {
21905
- path: Type16.Optional(
21906
- Type16.String({ description: "\u4F1A\u8BDD\u5377\u7EDD\u5BF9\u6216\u53EF\u8BFB\u8DEF\u5F84" })
21907
- ),
21908
- ranges: Type16.Optional(
21909
- Type16.Array(
21910
- Type16.Object(
21911
- {
21912
- from: Type16.Optional(
21913
- Type16.Object(
21914
- {
21915
- id: Type16.Optional(Type16.String()),
21916
- line: Type16.Optional(Type16.Unknown())
21917
- },
21918
- {
21919
- additionalProperties: true,
21920
- description: "\u8D77\u70B9\uFF1A\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00"
21921
- }
21922
- )
21923
- ),
21924
- to: Type16.Optional(
21925
- Type16.Object(
21926
- {
21927
- id: Type16.Optional(Type16.String()),
21928
- line: Type16.Optional(Type16.Unknown())
21929
- },
21930
- {
21931
- additionalProperties: true,
21932
- description: "\u7EC8\u70B9\uFF1A\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00"
21933
- }
21934
- )
21935
- )
21936
- },
21937
- { additionalProperties: true, description: "\u4E00\u6BB5\u5BF9\u8BDD\u533A\u95F4" }
21938
- ),
21939
- { description: "\u672C\u5377\u672C\u8F6E\u5404\u533A\u95F4\uFF1B\u81F3\u5C11\u4E00\u6BB5" }
21940
- )
21941
- )
21946
+ ticketNumber: Type16.Unknown({ description: "\u672C\u6761\u8FB9\u754C\u6240\u5F52\u7968\u53F7\uFF08\u6B63\u6574\u6570\uFF09" }),
21947
+ sessions: diaristSessionsSchema
21942
21948
  },
21943
- { additionalProperties: true, description: "\u4E00\u5377\u4F1A\u8BDD\u7684\u8FB9\u754C" }
21949
+ { additionalProperties: true }
21944
21950
  ),
21945
- {
21946
- description: "\u672C\u7968\u5BF9\u8BDD\u8FB9\u754C\uFF1B\u7A7A\u5217\u8868\uFF1D\u672C\u8F6E\u65E0\u5BF9\u8BDD\u53EF\u5212\u3002\u7AEF\u70B9\u65E0\u6CD5\u6307\u540D\u65F6\u8D70 reask\uFF0C\u4E0D\u4E2D\u6B62\u3002"
21947
- }
21951
+ { description: "\u591A\u7968\u5EAD\u9010\u7968\u5BF9\u8BDD\u8FB9\u754C\uFF1B\u4E00\u6B21\u4EA4\u5377\u5404\u7968\u5404\u81EA\u5206\u5F55\u3002\u4E0E\u5355\u7968 sessions \u4E8C\u9009\u4E00\u3002" }
21948
21952
  )
21949
21953
  )
21950
21954
  })
@@ -21953,7 +21957,7 @@ var diaristOutputSchema = withTerminatingOutputDeclarations(
21953
21957
  var DIARIST_TOOL_SPEC = {
21954
21958
  name: DIARIST_OUTPUT_TOOL_NAME,
21955
21959
  label: "\u8D77\u5C45\u90CE\u8F93\u51FA",
21956
- description: "\u8D77\u5C45\u90CE\u4EA4\u672C\u7968\u5BF9\u8BDD\u8FB9\u754C\uFF08sessions\uFF09\uFF1B\u8BA4\u4E0D\u51FA\u672C\u5EAD\u5BF9\u8C61\u5219 escalate\u3002",
21960
+ description: "\u8D77\u5C45\u90CE\u4EA4\u5355\u7968 sessions \u6216\u591A\u7968\u9010\u7968 ticketSessions \u8FB9\u754C\uFF1B\u8BA4\u4E0D\u51FA\u672C\u5EAD\u5BF9\u8C61\u5219 escalate\u3002",
21957
21961
  promptSnippet: "\u8D77\u5C45\u90CE\u4EA4\u8FB9\u754C",
21958
21962
  parameters: diaristOutputSchema
21959
21963
  };
@@ -22003,18 +22007,7 @@ init_diarist_contracts();
22003
22007
  // src/diarist.ts
22004
22008
  init_ticket_provenance();
22005
22009
  async function commitDiaristProjection(input) {
22006
- const result = await reprojectTicketProvenance({
22007
- ticketNumber: input.ticketNumber,
22008
- cwd: input.cwd,
22009
- ...input.home === void 0 ? {} : { home: input.home },
22010
- sessions: input.sessions,
22011
- ...input.runDirectory === void 0 ? {} : { runDirectory: input.runDirectory }
22012
- });
22013
- return {
22014
- ticketNumber: input.ticketNumber,
22015
- volumeRecordFile: result.recordFile,
22016
- lineCount: result.lines.length
22017
- };
22010
+ await reprojectTicketProvenanceBatch(input);
22018
22011
  }
22019
22012
 
22020
22013
  // src/role-runtime.ts
@@ -24122,7 +24115,7 @@ function readDiaristRunCoordinates(ctx) {
24122
24115
  ...bound === void 0 ? {} : { boundTicketNumber: bound }
24123
24116
  };
24124
24117
  }
24125
- var DIARIST_BOUNDS_REASK = "\u8FB9\u754C\u65E0\u6CD5\u4F7F\u7528\u3002\u8BF7\u91CD\u4EA4 sessions\uFF1A\u6BCF\u5377 path + ranges\uFF0C\u6BCF\u7AEF\u4EE5\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00\u6307\u540D\u3002";
24118
+ var DIARIST_BOUNDS_REASK = "\u8FB9\u754C\u65E0\u6CD5\u4F7F\u7528\u3002\u591A\u7968\u8BF7\u9010\u7968\u91CD\u4EA4 ticketSessions\uFF0C\u5355\u7968\u91CD\u4EA4 sessions\uFF1B\u6BCF\u5377 path + ranges\uFF0C\u6BCF\u7AEF\u4EE5\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00\u6307\u540D\u3002";
24126
24119
  function createDiaristRoleRuntime(roleHost, dependencies) {
24127
24120
  return createFiledOfficerRuntime(
24128
24121
  roleHost,
@@ -24142,18 +24135,17 @@ function createDiaristRoleRuntime(roleHost, dependencies) {
24142
24135
  await bindTicketNumberOnRunDirectory(coords.runDirectory, ticketNumber);
24143
24136
  }
24144
24137
  }
24145
- const sessions = projectDiaristSessions(parameters);
24146
- if (sessions === void 0) {
24138
+ const singleSessions = submitted && !Object.hasOwn(submitted, "ticketSessions") ? projectDiaristSessions(parameters) : void 0;
24139
+ const ticketSessions = submitted && Object.hasOwn(submitted, "ticketSessions") ? projectDiaristTicketSessions(submitted) : singleSessions === void 0 ? void 0 : [{ ticketNumber: ticketNumber ?? null, sessions: singleSessions }];
24140
+ if (ticketSessions === void 0) {
24147
24141
  throw new ParentQueueReaskError(DIARIST_BOUNDS_REASK);
24148
24142
  }
24149
- let facts;
24150
24143
  try {
24151
- facts = await commitDiaristProjection({
24152
- ticketNumber: ticketNumber ?? null,
24144
+ await commitDiaristProjection({
24153
24145
  cwd: coords.projectRoot,
24154
24146
  home: coords.home,
24155
24147
  runDirectory: coords.runDirectory,
24156
- sessions
24148
+ tickets: ticketSessions
24157
24149
  });
24158
24150
  } catch (error) {
24159
24151
  if (error instanceof ParentQueueReaskError) throw error;
@@ -130,3 +130,22 @@ export function projectDiaristSessions(value) {
130
130
  return [];
131
131
  return projectTicketProvenanceSessions(raw);
132
132
  }
133
+ /** Present multi-ticket bounds take precedence over the legacy single-ticket sessions. */
134
+ export function projectDiaristTicketSessions(value) {
135
+ const raw = value.ticketSessions;
136
+ if (!Array.isArray(raw))
137
+ return undefined;
138
+ const perTicket = new Map();
139
+ for (const entry of raw) {
140
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry))
141
+ return undefined;
142
+ const { ticketNumber, sessions: rawSessions } = entry;
143
+ if (!isSafePositiveTicketNumber(ticketNumber))
144
+ return undefined;
145
+ const sessions = projectTicketProvenanceSessions(rawSessions);
146
+ if (sessions === undefined)
147
+ return undefined;
148
+ perTicket.set(ticketNumber, [...(perTicket.get(ticketNumber) ?? []), ...sessions]);
149
+ }
150
+ return Array.from(perTicket, ([ticketNumber, sessions]) => ({ ticketNumber, sessions }));
151
+ }
@@ -934,6 +934,20 @@ function projectDiaristSessions(value) {
934
934
  if (raw === void 0) return [];
935
935
  return projectTicketProvenanceSessions(raw);
936
936
  }
937
+ function projectDiaristTicketSessions(value) {
938
+ const raw = value.ticketSessions;
939
+ if (!Array.isArray(raw)) return void 0;
940
+ const perTicket = /* @__PURE__ */ new Map();
941
+ for (const entry of raw) {
942
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return void 0;
943
+ const { ticketNumber, sessions: rawSessions } = entry;
944
+ if (!isSafePositiveTicketNumber(ticketNumber)) return void 0;
945
+ const sessions = projectTicketProvenanceSessions(rawSessions);
946
+ if (sessions === void 0) return void 0;
947
+ perTicket.set(ticketNumber, [...perTicket.get(ticketNumber) ?? [], ...sessions]);
948
+ }
949
+ return Array.from(perTicket, ([ticketNumber, sessions]) => ({ ticketNumber, sessions }));
950
+ }
937
951
  var DIARIST_OUTPUT_TOOL_NAME;
938
952
  var init_diarist_contracts = __esm({
939
953
  "src/diarist-contracts.ts"() {
@@ -5768,42 +5782,37 @@ async function projectSessionRanges(input) {
5768
5782
  }
5769
5783
  return { entries };
5770
5784
  }
5771
- async function reprojectTicketProvenance(input) {
5772
- const recordInput = ticketProvenanceRecordInput(
5773
- input.ticketNumber,
5774
- input.cwd,
5775
- input.home,
5776
- input.runDirectory
5777
- );
5778
- const resolved = resolveSitianRecordPath(recordInput);
5779
- if (input.sessions.length === 0) {
5780
- return {
5781
- recordFile: resolved.recordFile,
5782
- lines: []
5783
- };
5785
+ async function reprojectTicketProvenanceBatch(input) {
5786
+ const projections = [];
5787
+ for (const ticket of input.tickets) {
5788
+ const recordInput = ticketProvenanceRecordInput(
5789
+ ticket.ticketNumber,
5790
+ input.cwd,
5791
+ input.home,
5792
+ input.runDirectory
5793
+ );
5794
+ const entries = [];
5795
+ for (let s = 0; s < ticket.sessions.length; s += 1) {
5796
+ const projected = await projectSessionRanges({
5797
+ s,
5798
+ session: ticket.sessions[s],
5799
+ ...input.home === void 0 ? {} : { home: input.home }
5800
+ });
5801
+ entries.push(...projected.entries);
5802
+ }
5803
+ projections.push({ ticket, recordInput, entries });
5784
5804
  }
5785
- const entries = [];
5786
- for (let s = 0; s < input.sessions.length; s += 1) {
5787
- const session = input.sessions[s];
5788
- const projected = await projectSessionRanges({
5789
- s,
5790
- session,
5791
- ...input.home === void 0 ? {} : { home: input.home }
5805
+ for (const { ticket, recordInput, entries } of projections) {
5806
+ if (ticket.sessions.length === 0) continue;
5807
+ appendSitianRecord({
5808
+ ...recordInput,
5809
+ payload: {
5810
+ type: "ticket-provenance-append",
5811
+ sessions: ticket.sessions,
5812
+ lines: entries
5813
+ }
5792
5814
  });
5793
- entries.push(...projected.entries);
5794
5815
  }
5795
- const pointer = appendSitianRecord({
5796
- ...recordInput,
5797
- payload: {
5798
- type: "ticket-provenance-append",
5799
- sessions: input.sessions,
5800
- lines: entries
5801
- }
5802
- });
5803
- return {
5804
- recordFile: pointer.recordFile,
5805
- lines: entries.filter((entry) => "speaker" in entry)
5806
- };
5807
5816
  }
5808
5817
  var TicketProvenanceInputError;
5809
5818
  var init_ticket_provenance = __esm({
@@ -22935,6 +22944,37 @@ init_terminating_infrastructure();
22935
22944
  init_diarist_contracts();
22936
22945
  init_diarist_contracts();
22937
22946
  import { Type as Type18 } from "typebox";
22947
+ var diaristSessionsSchema = Type18.Array(
22948
+ Type18.Object(
22949
+ {
22950
+ path: Type18.Optional(Type18.String({ description: "\u4F1A\u8BDD\u5377\u7EDD\u5BF9\u6216\u53EF\u8BFB\u8DEF\u5F84" })),
22951
+ ranges: Type18.Optional(
22952
+ Type18.Array(
22953
+ Type18.Object(
22954
+ {
22955
+ from: Type18.Optional(
22956
+ Type18.Object(
22957
+ { id: Type18.Optional(Type18.String()), line: Type18.Optional(Type18.Unknown()) },
22958
+ { additionalProperties: true, description: "\u8D77\u70B9\uFF1A\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00" }
22959
+ )
22960
+ ),
22961
+ to: Type18.Optional(
22962
+ Type18.Object(
22963
+ { id: Type18.Optional(Type18.String()), line: Type18.Optional(Type18.Unknown()) },
22964
+ { additionalProperties: true, description: "\u7EC8\u70B9\uFF1A\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00" }
22965
+ )
22966
+ )
22967
+ },
22968
+ { additionalProperties: true, description: "\u4E00\u6BB5\u5BF9\u8BDD\u533A\u95F4" }
22969
+ ),
22970
+ { description: "\u672C\u5377\u672C\u8F6E\u5404\u533A\u95F4\uFF1B\u81F3\u5C11\u4E00\u6BB5" }
22971
+ )
22972
+ )
22973
+ },
22974
+ { additionalProperties: true, description: "\u4E00\u5377\u4F1A\u8BDD\u7684\u8FB9\u754C" }
22975
+ ),
22976
+ { description: "\u5BF9\u8BDD\u8FB9\u754C\uFF1B\u7A7A\u5217\u8868\uFF1D\u672C\u8F6E\u65E0\u5BF9\u8BDD\u53EF\u5212\u3002\u7AEF\u70B9\u65E0\u6CD5\u6307\u540D\u65F6\u8D70 reask\uFF0C\u4E0D\u4E2D\u6B62\u3002" }
22977
+ );
22938
22978
  var diaristOutputSchema = withTerminatingOutputDeclarations(
22939
22979
  openToolObject(
22940
22980
  Type18.Object({
@@ -22956,53 +22996,17 @@ var diaristOutputSchema = withTerminatingOutputDeclarations(
22956
22996
  description: "status \u4E3A escalate \u65F6\uFF1A\u8BA4\u4E0D\u51FA\u672C\u5EAD\u5BF9\u8C61\u7684\u539F\u56E0"
22957
22997
  })
22958
22998
  ),
22959
- sessions: Type18.Optional(
22999
+ sessions: Type18.Optional(diaristSessionsSchema),
23000
+ ticketSessions: Type18.Optional(
22960
23001
  Type18.Array(
22961
23002
  Type18.Object(
22962
23003
  {
22963
- path: Type18.Optional(
22964
- Type18.String({ description: "\u4F1A\u8BDD\u5377\u7EDD\u5BF9\u6216\u53EF\u8BFB\u8DEF\u5F84" })
22965
- ),
22966
- ranges: Type18.Optional(
22967
- Type18.Array(
22968
- Type18.Object(
22969
- {
22970
- from: Type18.Optional(
22971
- Type18.Object(
22972
- {
22973
- id: Type18.Optional(Type18.String()),
22974
- line: Type18.Optional(Type18.Unknown())
22975
- },
22976
- {
22977
- additionalProperties: true,
22978
- description: "\u8D77\u70B9\uFF1A\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00"
22979
- }
22980
- )
22981
- ),
22982
- to: Type18.Optional(
22983
- Type18.Object(
22984
- {
22985
- id: Type18.Optional(Type18.String()),
22986
- line: Type18.Optional(Type18.Unknown())
22987
- },
22988
- {
22989
- additionalProperties: true,
22990
- description: "\u7EC8\u70B9\uFF1A\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00"
22991
- }
22992
- )
22993
- )
22994
- },
22995
- { additionalProperties: true, description: "\u4E00\u6BB5\u5BF9\u8BDD\u533A\u95F4" }
22996
- ),
22997
- { description: "\u672C\u5377\u672C\u8F6E\u5404\u533A\u95F4\uFF1B\u81F3\u5C11\u4E00\u6BB5" }
22998
- )
22999
- )
23004
+ ticketNumber: Type18.Unknown({ description: "\u672C\u6761\u8FB9\u754C\u6240\u5F52\u7968\u53F7\uFF08\u6B63\u6574\u6570\uFF09" }),
23005
+ sessions: diaristSessionsSchema
23000
23006
  },
23001
- { additionalProperties: true, description: "\u4E00\u5377\u4F1A\u8BDD\u7684\u8FB9\u754C" }
23007
+ { additionalProperties: true }
23002
23008
  ),
23003
- {
23004
- description: "\u672C\u7968\u5BF9\u8BDD\u8FB9\u754C\uFF1B\u7A7A\u5217\u8868\uFF1D\u672C\u8F6E\u65E0\u5BF9\u8BDD\u53EF\u5212\u3002\u7AEF\u70B9\u65E0\u6CD5\u6307\u540D\u65F6\u8D70 reask\uFF0C\u4E0D\u4E2D\u6B62\u3002"
23005
- }
23009
+ { description: "\u591A\u7968\u5EAD\u9010\u7968\u5BF9\u8BDD\u8FB9\u754C\uFF1B\u4E00\u6B21\u4EA4\u5377\u5404\u7968\u5404\u81EA\u5206\u5F55\u3002\u4E0E\u5355\u7968 sessions \u4E8C\u9009\u4E00\u3002" }
23006
23010
  )
23007
23011
  )
23008
23012
  })
@@ -23011,7 +23015,7 @@ var diaristOutputSchema = withTerminatingOutputDeclarations(
23011
23015
  var DIARIST_TOOL_SPEC = {
23012
23016
  name: DIARIST_OUTPUT_TOOL_NAME,
23013
23017
  label: "\u8D77\u5C45\u90CE\u8F93\u51FA",
23014
- description: "\u8D77\u5C45\u90CE\u4EA4\u672C\u7968\u5BF9\u8BDD\u8FB9\u754C\uFF08sessions\uFF09\uFF1B\u8BA4\u4E0D\u51FA\u672C\u5EAD\u5BF9\u8C61\u5219 escalate\u3002",
23018
+ description: "\u8D77\u5C45\u90CE\u4EA4\u5355\u7968 sessions \u6216\u591A\u7968\u9010\u7968 ticketSessions \u8FB9\u754C\uFF1B\u8BA4\u4E0D\u51FA\u672C\u5EAD\u5BF9\u8C61\u5219 escalate\u3002",
23015
23019
  promptSnippet: "\u8D77\u5C45\u90CE\u4EA4\u8FB9\u754C",
23016
23020
  parameters: diaristOutputSchema
23017
23021
  };
@@ -23061,18 +23065,7 @@ init_diarist_contracts();
23061
23065
  // src/diarist.ts
23062
23066
  init_ticket_provenance();
23063
23067
  async function commitDiaristProjection(input) {
23064
- const result = await reprojectTicketProvenance({
23065
- ticketNumber: input.ticketNumber,
23066
- cwd: input.cwd,
23067
- ...input.home === void 0 ? {} : { home: input.home },
23068
- sessions: input.sessions,
23069
- ...input.runDirectory === void 0 ? {} : { runDirectory: input.runDirectory }
23070
- });
23071
- return {
23072
- ticketNumber: input.ticketNumber,
23073
- volumeRecordFile: result.recordFile,
23074
- lineCount: result.lines.length
23075
- };
23068
+ await reprojectTicketProvenanceBatch(input);
23076
23069
  }
23077
23070
 
23078
23071
  // src/role-runtime.ts
@@ -24215,7 +24208,7 @@ function readDiaristRunCoordinates(ctx) {
24215
24208
  ...bound === void 0 ? {} : { boundTicketNumber: bound }
24216
24209
  };
24217
24210
  }
24218
- var DIARIST_BOUNDS_REASK = "\u8FB9\u754C\u65E0\u6CD5\u4F7F\u7528\u3002\u8BF7\u91CD\u4EA4 sessions\uFF1A\u6BCF\u5377 path + ranges\uFF0C\u6BCF\u7AEF\u4EE5\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00\u6307\u540D\u3002";
24211
+ var DIARIST_BOUNDS_REASK = "\u8FB9\u754C\u65E0\u6CD5\u4F7F\u7528\u3002\u591A\u7968\u8BF7\u9010\u7968\u91CD\u4EA4 ticketSessions\uFF0C\u5355\u7968\u91CD\u4EA4 sessions\uFF1B\u6BCF\u5377 path + ranges\uFF0C\u6BCF\u7AEF\u4EE5\u539F\u751F id \u6216\u672C\u8F6E\u884C\u53F7\u4E8C\u9009\u4E00\u6307\u540D\u3002";
24219
24212
  function createDiaristRoleRuntime(roleHost, dependencies) {
24220
24213
  return createFiledOfficerRuntime(
24221
24214
  roleHost,
@@ -24235,18 +24228,17 @@ function createDiaristRoleRuntime(roleHost, dependencies) {
24235
24228
  await bindTicketNumberOnRunDirectory(coords.runDirectory, ticketNumber);
24236
24229
  }
24237
24230
  }
24238
- const sessions = projectDiaristSessions(parameters);
24239
- if (sessions === void 0) {
24231
+ const singleSessions = submitted && !Object.hasOwn(submitted, "ticketSessions") ? projectDiaristSessions(parameters) : void 0;
24232
+ const ticketSessions = submitted && Object.hasOwn(submitted, "ticketSessions") ? projectDiaristTicketSessions(submitted) : singleSessions === void 0 ? void 0 : [{ ticketNumber: ticketNumber ?? null, sessions: singleSessions }];
24233
+ if (ticketSessions === void 0) {
24240
24234
  throw new ParentQueueReaskError(DIARIST_BOUNDS_REASK);
24241
24235
  }
24242
- let facts;
24243
24236
  try {
24244
- facts = await commitDiaristProjection({
24245
- ticketNumber: ticketNumber ?? null,
24237
+ await commitDiaristProjection({
24246
24238
  cwd: coords.projectRoot,
24247
24239
  home: coords.home,
24248
24240
  runDirectory: coords.runDirectory,
24249
- sessions
24241
+ tickets: ticketSessions
24250
24242
  });
24251
24243
  } catch (error) {
24252
24244
  if (error instanceof ParentQueueReaskError) throw error;
@@ -231,39 +231,35 @@ async function projectSessionRanges(input) {
231
231
  return { entries };
232
232
  }
233
233
  /**
234
- * Append dialogue projection from LLM-submitted bounds into records.jsonl.
234
+ * Project every submitted boundary before appending any ticket's record.
235
235
  * #1090: 纯追加——不回读已成录历史查重、不折叠、不刷新;同一区间再次提交也留下新的追加记录。
236
236
  * 证不出的 body 原字节原样留卷。
237
237
  */
238
- export async function reprojectTicketProvenance(input) {
239
- const recordInput = ticketProvenanceRecordInput(input.ticketNumber, input.cwd, input.home, input.runDirectory);
240
- const resolved = resolveSitianRecordPath(recordInput);
241
- if (input.sessions.length === 0) {
242
- return {
243
- recordFile: resolved.recordFile,
244
- lines: [],
245
- };
238
+ export async function reprojectTicketProvenanceBatch(input) {
239
+ const projections = [];
240
+ for (const ticket of input.tickets) {
241
+ const recordInput = ticketProvenanceRecordInput(ticket.ticketNumber, input.cwd, input.home, input.runDirectory);
242
+ const entries = [];
243
+ for (let s = 0; s < ticket.sessions.length; s += 1) {
244
+ const projected = await projectSessionRanges({
245
+ s,
246
+ session: ticket.sessions[s],
247
+ ...(input.home === undefined ? {} : { home: input.home }),
248
+ });
249
+ entries.push(...projected.entries);
250
+ }
251
+ projections.push({ ticket, recordInput, entries });
246
252
  }
247
- const entries = [];
248
- for (let s = 0; s < input.sessions.length; s += 1) {
249
- const session = input.sessions[s];
250
- const projected = await projectSessionRanges({
251
- s,
252
- session,
253
- ...(input.home === undefined ? {} : { home: input.home }),
253
+ for (const { ticket, recordInput, entries } of projections) {
254
+ if (ticket.sessions.length === 0)
255
+ continue;
256
+ appendSitianRecord({
257
+ ...recordInput,
258
+ payload: {
259
+ type: "ticket-provenance-append",
260
+ sessions: ticket.sessions,
261
+ lines: entries,
262
+ },
254
263
  });
255
- entries.push(...projected.entries);
256
264
  }
257
- const pointer = appendSitianRecord({
258
- ...recordInput,
259
- payload: {
260
- type: "ticket-provenance-append",
261
- sessions: input.sessions,
262
- lines: entries,
263
- },
264
- });
265
- return {
266
- recordFile: pointer.recordFile,
267
- lines: entries.filter((entry) => "speaker" in entry),
268
- };
269
265
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5289",
3
+ "version": "0.1.5295",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 任务
6
6
 
7
- 判断本庭对象是哪张票、本票对话起止于何处并交出边界;正文与定位由机械按边界从会话卷投影。不整块誊录正文,不自行搜集材料替代机械投影;本文件不新立职掌、不替 soul 立法、不复制 schema 与 CLI 说明。
7
+ 判断本庭要办哪些票、每票对话起止于何处并交出各自边界;正文与定位由机械按边界从会话卷投影。不整块誊录正文,不自行搜集材料替代机械投影;本文件不新立职掌、不替 soul 立法、不复制 schema 与 CLI 说明。
8
8
 
9
9
  ## 如何定边界
10
10
 
@@ -13,7 +13,7 @@
13
13
  - 本仓相关会话卷(路径里的 `_` 在目录名里常写作 `-`;主 clone 与 worktree 可能共用或分列,自行辨认)
14
14
  - 本票票面与评论:用 `gh` 或等价方式读 GitHub issue
15
15
 
16
- 调用方只给传召文与 `--project`。边界定不下来就如实说明并走既有 reask/escalate,不编造。
16
+ 调用方只给传召文与 `--project`。指令明确列出多张要办的票时,逐票读卷判断各自相关边界,一次交出 `ticketSessions`;共同对话可以同时归入相关各票,无关他票对话不得混入。单票沿用 `sessions`。边界定不下来就如实说明并走既有 reask/escalate,不编造。
17
17
 
18
18
  ## 何谓本票对话
19
19
 
@@ -29,11 +29,11 @@
29
29
 
30
30
  ## 本票身份
31
31
 
32
- 由你(本席 LLM)判断当前要办的是哪张票,在交卷对象上以 typed 字段交出——代码不做认票裁定(ADR 0075:起居郎 LLM 自行认票;认得出→有录,真无票→无录;机械层不做引文/票号/相关性判定)。
32
+ 由你(本席 LLM)判断当前要办的票;多票庭逐票划界,同时照旧申报本庭主票身份,在交卷对象上以 typed 字段交出——代码不做认票裁定(ADR 0075:起居郎 LLM 自行认票;认得出→有录,真无票→无录;机械层不做引文/票号/相关性判定)。
33
33
 
34
34
  - 认得出本庭对象:`status: completed` 且 `ticketNumber: N`,并交边界。
35
35
  - 真无票对象(方案/派单/处置案等):`status: completed` 且 `ticketNumber: null`(或省略)。真无票是合法结果,不入录。
36
36
  - 本庭明确要新建票、但 issue 尚未建号:不是认票失败。以 `status: completed`、`ticketNumber: null` 交出已辨明的本案对话边界;起居录先随无号 run 暂存,建号后由既有归卷流程补归该号。不要借用旁票号。
37
37
  - 认不出本庭对象是哪一张:`status: escalate` 并写明 `reason`。识别不了就上抛,不得省略票号假装无录,也不得猜一个号。
38
38
  - 不得从旁及的他票或任何猜测里挑一个号顶替认不出的身份。
39
- - 传召文自然会提轮次、决定编号、commit sha、邻票号;你认的是本庭对象,不是文中出现的每一个数字。
39
+ - 传召文自然会提轮次、决定编号、commit sha、邻票号;要办的是指令明确列出的票,不是文中出现的每一个数字。
@@ -42,6 +42,11 @@ export type DiaristOutput =
42
42
  * Malformed endpoints → accept hook reasks (`reask-not-explode`).
43
43
  */
44
44
  readonly sessions?: readonly TicketProvenanceSession[];
45
+ /** Per-ticket dialogue bounds for a single multi-ticket summons (#1107). */
46
+ readonly ticketSessions?: readonly {
47
+ readonly ticketNumber: number;
48
+ readonly sessions: readonly TicketProvenanceSession[];
49
+ }[];
45
50
  }
46
51
  | {
47
52
  /** LLM cannot tell which ticket this summons is about — escalate, never wash into 无录. */
@@ -179,3 +184,21 @@ export function projectDiaristSessions(
179
184
  if (raw === undefined) return [];
180
185
  return projectTicketProvenanceSessions(raw);
181
186
  }
187
+
188
+ /** Present multi-ticket bounds take precedence over the legacy single-ticket sessions. */
189
+ export function projectDiaristTicketSessions(
190
+ value: Record<string, unknown>,
191
+ ): readonly { readonly ticketNumber: number; readonly sessions: readonly TicketProvenanceSession[] }[] | undefined {
192
+ const raw = value.ticketSessions;
193
+ if (!Array.isArray(raw)) return undefined;
194
+ const perTicket = new Map<number, TicketProvenanceSession[]>();
195
+ for (const entry of raw) {
196
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return undefined;
197
+ const { ticketNumber, sessions: rawSessions } = entry as Record<string, unknown>;
198
+ if (!isSafePositiveTicketNumber(ticketNumber)) return undefined;
199
+ const sessions = projectTicketProvenanceSessions(rawSessions);
200
+ if (sessions === undefined) return undefined;
201
+ perTicket.set(ticketNumber, [...(perTicket.get(ticketNumber) ?? []), ...sessions]);
202
+ }
203
+ return Array.from(perTicket, ([ticketNumber, sessions]) => ({ ticketNumber, sessions }));
204
+ }
@@ -19,6 +19,38 @@ export { validateRecordedDiaristOutput };
19
19
  * 起居郎交卷形状。
20
20
  * #901:交边界(sessions);正文与不可解析原字节由机械投影。
21
21
  */
22
+ const diaristSessionsSchema = Type.Array(
23
+ Type.Object(
24
+ {
25
+ path: Type.Optional(Type.String({ description: "会话卷绝对或可读路径" })),
26
+ ranges: Type.Optional(
27
+ Type.Array(
28
+ Type.Object(
29
+ {
30
+ from: Type.Optional(
31
+ Type.Object(
32
+ { id: Type.Optional(Type.String()), line: Type.Optional(Type.Unknown()) },
33
+ { additionalProperties: true, description: "起点:原生 id 或本轮行号二选一" },
34
+ ),
35
+ ),
36
+ to: Type.Optional(
37
+ Type.Object(
38
+ { id: Type.Optional(Type.String()), line: Type.Optional(Type.Unknown()) },
39
+ { additionalProperties: true, description: "终点:原生 id 或本轮行号二选一" },
40
+ ),
41
+ ),
42
+ },
43
+ { additionalProperties: true, description: "一段对话区间" },
44
+ ),
45
+ { description: "本卷本轮各区间;至少一段" },
46
+ ),
47
+ ),
48
+ },
49
+ { additionalProperties: true, description: "一卷会话的边界" },
50
+ ),
51
+ { description: "对话边界;空列表=本轮无对话可划。端点无法指名时走 reask,不中止。" },
52
+ );
53
+
22
54
  export const diaristOutputSchema = withTerminatingOutputDeclarations(
23
55
  openToolObject(
24
56
  Type.Object({
@@ -42,54 +74,17 @@ export const diaristOutputSchema = withTerminatingOutputDeclarations(
42
74
  description: "status 为 escalate 时:认不出本庭对象的原因",
43
75
  }),
44
76
  ),
45
- sessions: Type.Optional(
77
+ sessions: Type.Optional(diaristSessionsSchema),
78
+ ticketSessions: Type.Optional(
46
79
  Type.Array(
47
80
  Type.Object(
48
81
  {
49
- path: Type.Optional(
50
- Type.String({ description: "会话卷绝对或可读路径" }),
51
- ),
52
- ranges: Type.Optional(
53
- Type.Array(
54
- Type.Object(
55
- {
56
- from: Type.Optional(
57
- Type.Object(
58
- {
59
- id: Type.Optional(Type.String()),
60
- line: Type.Optional(Type.Unknown()),
61
- },
62
- {
63
- additionalProperties: true,
64
- description: "起点:原生 id 或本轮行号二选一",
65
- },
66
- ),
67
- ),
68
- to: Type.Optional(
69
- Type.Object(
70
- {
71
- id: Type.Optional(Type.String()),
72
- line: Type.Optional(Type.Unknown()),
73
- },
74
- {
75
- additionalProperties: true,
76
- description: "终点:原生 id 或本轮行号二选一",
77
- },
78
- ),
79
- ),
80
- },
81
- { additionalProperties: true, description: "一段对话区间" },
82
- ),
83
- { description: "本卷本轮各区间;至少一段" },
84
- ),
85
- ),
82
+ ticketNumber: Type.Unknown({ description: "本条边界所归票号(正整数)" }),
83
+ sessions: diaristSessionsSchema,
86
84
  },
87
- { additionalProperties: true, description: "一卷会话的边界" },
85
+ { additionalProperties: true },
88
86
  ),
89
- {
90
- description:
91
- "本票对话边界;空列表=本轮无对话可划。端点无法指名时走 reask,不中止。",
92
- },
87
+ { description: "多票庭逐票对话边界;一次交卷各票各自分录。与单票 sessions 二选一。" },
93
88
  ),
94
89
  ),
95
90
  }),
@@ -109,7 +104,7 @@ export const DIARIST_TOOL_SPEC = {
109
104
  name: DIARIST_OUTPUT_TOOL_NAME,
110
105
  label: "起居郎输出",
111
106
  description:
112
- "起居郎交本票对话边界(sessions);认不出本庭对象则 escalate。",
107
+ "起居郎交单票 sessions 或多票逐票 ticketSessions 边界;认不出本庭对象则 escalate。",
113
108
  promptSnippet: "起居郎交边界",
114
109
  parameters: diaristOutputSchema,
115
110
  } as const;
package/src/diarist.ts CHANGED
@@ -4,42 +4,22 @@
4
4
  * session volumes (pure append, no cross-submit dedupe). No human view.
5
5
  * No lifecycle here (ADR 0018): the seat prepares, the role envelope commits.
6
6
  */
7
- import { reprojectTicketProvenance } from "./ticket-provenance.ts";
7
+ import { reprojectTicketProvenanceBatch } from "./ticket-provenance.ts";
8
8
  import type { TicketProvenanceSession } from "./ticket-provenance-contracts.ts";
9
9
 
10
10
  /**
11
- * Honest machine facts about what this turn actually committed to the volume.
12
- * Populated by the envelope accept hook — never from model self-report.
13
- */
14
- export type DiaristCommitFacts = {
15
- readonly ticketNumber: number | null;
16
- readonly volumeRecordFile: string;
17
- /** Dialogue lines written by this reproject. */
18
- readonly lineCount: number;
19
- };
20
-
21
- /**
22
- * Append a diary projection from LLM-submitted bounds (#1090).
11
+ * Append per-ticket diary projections from LLM-submitted bounds (#1090 / #1107).
23
12
  * Shape projection of sessions is the caller's job (accept hook);
24
- * this seam only copies source facts into the volume.
13
+ * this seam only copies source facts into each ticket's volume.
25
14
  */
26
15
  export async function commitDiaristProjection(input: {
27
- readonly ticketNumber: number | null;
28
16
  readonly cwd: string;
29
17
  readonly home?: string;
30
18
  readonly runDirectory?: string;
31
- readonly sessions: readonly TicketProvenanceSession[];
32
- }): Promise<DiaristCommitFacts> {
33
- const result = await reprojectTicketProvenance({
34
- ticketNumber: input.ticketNumber,
35
- cwd: input.cwd,
36
- ...(input.home === undefined ? {} : { home: input.home }),
37
- sessions: input.sessions,
38
- ...(input.runDirectory === undefined ? {} : { runDirectory: input.runDirectory }),
39
- });
40
- return {
41
- ticketNumber: input.ticketNumber,
42
- volumeRecordFile: result.recordFile,
43
- lineCount: result.lines.length,
44
- };
19
+ readonly tickets: readonly {
20
+ readonly ticketNumber: number | null;
21
+ readonly sessions: readonly TicketProvenanceSession[];
22
+ }[];
23
+ }): Promise<void> {
24
+ await reprojectTicketProvenanceBatch(input);
45
25
  }
@@ -73,6 +73,7 @@ import {
73
73
  import { SECRETARIAT_OUTPUT_TOOL_NAME } from "./secretariat-contracts.ts";
74
74
  import {
75
75
  projectDiaristSessions,
76
+ projectDiaristTicketSessions,
76
77
  } from "./diarist-contracts.ts";
77
78
  import { commitDiaristProjection } from "./diarist.ts";
78
79
  import { TicketProvenanceInputError } from "./ticket-provenance.ts";
@@ -834,7 +835,7 @@ function readDiaristRunCoordinates(ctx: HostContext): {
834
835
 
835
836
  /** Plain-language re-ask when diarist bounds cannot be used (#901 / reask-not-explode). */
836
837
  const DIARIST_BOUNDS_REASK =
837
- "边界无法使用。请重交 sessions:每卷 path + ranges,每端以原生 id 或本轮行号二选一指名。" as const;
838
+ "边界无法使用。多票请逐票重交 ticketSessions,单票重交 sessions;每卷 path + ranges,每端以原生 id 或本轮行号二选一指名。" as const;
838
839
  /**
839
840
  * #708 / #779 / #901: 起居郎 public seat on the shared filed-officer envelope.
840
841
  * LLM judges ticket + dialogue bounds; mechanical layer reprojects the unique
@@ -870,18 +871,23 @@ export function createDiaristRoleRuntime(
870
871
  await bindTicketNumberOnRunDirectory(coords.runDirectory, ticketNumber);
871
872
  }
872
873
  }
873
- const sessions = projectDiaristSessions(parameters);
874
- if (sessions === undefined) {
874
+ const singleSessions = submitted && !Object.hasOwn(submitted, "ticketSessions")
875
+ ? projectDiaristSessions(parameters)
876
+ : undefined;
877
+ const ticketSessions = submitted && Object.hasOwn(submitted, "ticketSessions")
878
+ ? projectDiaristTicketSessions(submitted)
879
+ : singleSessions === undefined
880
+ ? undefined
881
+ : [{ ticketNumber: ticketNumber ?? null, sessions: singleSessions }];
882
+ if (ticketSessions === undefined) {
875
883
  throw new ParentQueueReaskError(DIARIST_BOUNDS_REASK);
876
884
  }
877
- let facts;
878
885
  try {
879
- facts = await commitDiaristProjection({
880
- ticketNumber: ticketNumber ?? null,
886
+ await commitDiaristProjection({
881
887
  cwd: coords.projectRoot,
882
888
  home: coords.home,
883
889
  runDirectory: coords.runDirectory,
884
- sessions,
890
+ tickets: ticketSessions,
885
891
  });
886
892
  } catch (error) {
887
893
  // Bound/session input failures → reask via typed identity (not message prefix).
@@ -162,11 +162,6 @@ export async function rehomeUnboundTicketProvenance(
162
162
  await unlink(source);
163
163
  }
164
164
 
165
- export type ReprojectTicketProvenanceResult = {
166
- readonly recordFile: string;
167
- readonly lines: readonly TicketProvenanceLine[];
168
- };
169
-
170
165
  /**
171
166
  * Resolve a bound endpoint against this round's session lines.
172
167
  * id → first physical line carrying that native id; line → that 1-based line.
@@ -327,53 +322,45 @@ async function projectSessionRanges(input: {
327
322
  }
328
323
 
329
324
  /**
330
- * Append dialogue projection from LLM-submitted bounds into records.jsonl.
325
+ * Project every submitted boundary before appending any ticket's record.
331
326
  * #1090: 纯追加——不回读已成录历史查重、不折叠、不刷新;同一区间再次提交也留下新的追加记录。
332
327
  * 证不出的 body 原字节原样留卷。
333
328
  */
334
- export async function reprojectTicketProvenance(input: {
335
- readonly ticketNumber: number | null;
329
+ export async function reprojectTicketProvenanceBatch(input: {
336
330
  readonly cwd: string;
337
331
  readonly home?: string;
338
332
  readonly runDirectory?: string;
339
- readonly sessions: readonly TicketProvenanceSession[];
340
- }): Promise<ReprojectTicketProvenanceResult> {
341
- const recordInput = ticketProvenanceRecordInput(
342
- input.ticketNumber,
343
- input.cwd,
344
- input.home,
345
- input.runDirectory,
346
- );
347
- const resolved = resolveSitianRecordPath(recordInput);
348
- if (input.sessions.length === 0) {
349
- return {
350
- recordFile: resolved.recordFile,
351
- lines: [],
352
- };
333
+ readonly tickets: readonly {
334
+ readonly ticketNumber: number | null;
335
+ readonly sessions: readonly TicketProvenanceSession[];
336
+ }[];
337
+ }): Promise<void> {
338
+ const projections = [];
339
+ for (const ticket of input.tickets) {
340
+ const recordInput = ticketProvenanceRecordInput(
341
+ ticket.ticketNumber, input.cwd, input.home, input.runDirectory,
342
+ );
343
+ const entries: (TicketProvenanceLine | TicketProvenanceRaw)[] = [];
344
+ for (let s = 0; s < ticket.sessions.length; s += 1) {
345
+ const projected = await projectSessionRanges({
346
+ s,
347
+ session: ticket.sessions[s]!,
348
+ ...(input.home === undefined ? {} : { home: input.home }),
349
+ });
350
+ entries.push(...projected.entries);
351
+ }
352
+ projections.push({ ticket, recordInput, entries });
353
353
  }
354
354
 
355
- const entries: (TicketProvenanceLine | TicketProvenanceRaw)[] = [];
356
- for (let s = 0; s < input.sessions.length; s += 1) {
357
- const session = input.sessions[s]!;
358
- const projected = await projectSessionRanges({
359
- s,
360
- session,
361
- ...(input.home === undefined ? {} : { home: input.home }),
355
+ for (const { ticket, recordInput, entries } of projections) {
356
+ if (ticket.sessions.length === 0) continue;
357
+ appendSitianRecord({
358
+ ...recordInput,
359
+ payload: {
360
+ type: "ticket-provenance-append",
361
+ sessions: ticket.sessions,
362
+ lines: entries,
363
+ },
362
364
  });
363
- entries.push(...projected.entries);
364
365
  }
365
-
366
- const pointer = appendSitianRecord({
367
- ...recordInput,
368
- payload: {
369
- type: "ticket-provenance-append",
370
- sessions: input.sessions,
371
- lines: entries,
372
- },
373
- });
374
-
375
- return {
376
- recordFile: pointer.recordFile,
377
- lines: entries.filter((entry): entry is TicketProvenanceLine => "speaker" in entry),
378
- };
379
366
  }