@rivus/agent 0.16.0 → 0.16.2

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.
@@ -3423,10 +3423,17 @@ function toDomainEvent(runId, sessionKey, event, occurredAt) {
3423
3423
  //#endregion
3424
3424
  //#region src/core/application/agent-execution/execution/agent-harness.ts
3425
3425
  function createSteeringChannel() {
3426
- return Queue.unbounded().pipe(Effect.map((queue) => ({
3427
- next: () => Queue.take(queue),
3428
- push: (text) => Queue.offer(queue, text).pipe(Effect.asVoid)
3429
- })));
3426
+ return Queue.unbounded().pipe(Effect.map((queue) => {
3427
+ let closed = false;
3428
+ return {
3429
+ next: () => Queue.take(queue),
3430
+ close: () => Effect.gen(function* () {
3431
+ closed = true;
3432
+ return Array.from(yield* Queue.takeAll(queue));
3433
+ }),
3434
+ push: (text) => Effect.sync(() => !closed && Queue.unsafeOffer(queue, text))
3435
+ };
3436
+ }));
3430
3437
  }
3431
3438
  function createAgentHarness$1(options) {
3432
3439
  if (options.runTimeoutMs !== void 0 && (!Number.isSafeInteger(options.runTimeoutMs) || options.runTimeoutMs < 1)) throw new Error("Agent run timeout must be a positive integer");
@@ -3643,8 +3650,7 @@ function createAgentHarness$1(options) {
3643
3650
  const requestSteering = (runId, text) => Effect.gen(function* () {
3644
3651
  const normalized = text.trim();
3645
3652
  if (!normalized || !options.loop.supportsSteering || !activeRun || activeRun.runId !== runId || !activeCancellation || activeCancellation.requested) return false;
3646
- yield* activeCancellation.steering.push(normalized);
3647
- return true;
3653
+ return yield* activeCancellation.steering.push(normalized);
3648
3654
  });
3649
3655
  const requestSessionCancellation = (sessionKey, runId, reason) => {
3650
3656
  if (!activeRun || activeRun.sessionKey !== sessionKey) return Effect.succeed(false);
@@ -5588,9 +5594,207 @@ function cardPresentationLeaseDeadline(at, leaseMs) {
5588
5594
  return new Date(atMs + validateCardPresentationLeaseMs(leaseMs)).toISOString();
5589
5595
  }
5590
5596
  //#endregion
5597
+ //#region src/adapters/feishu/run-presentation/feishu-run-presentation-renderer.ts
5598
+ function renderFeishuRunProgress(presentation) {
5599
+ const visible = presentation.steps.filter(isVisibleStep);
5600
+ const running = [...visible].reverse().find((step) => step.status === "running");
5601
+ const title = visible.length === 0 ? "执行过程 · 展开查看" : presentation.phase === "running" && running ? `执行过程 · ${runningLabel(running)}` : `执行过程 · ${visible.length} 段 · ${presentation.totalToolCallCount} 次工具调用`;
5602
+ return {
5603
+ markdown: [...presentation.omittedStepCount > 0 ? [`> 另有 ${presentation.omittedStepCount} 个较早步骤未展示`] : [], ...renderProgressSteps(visible)].join("\n\n") || "正在准备执行…",
5604
+ title
5605
+ };
5606
+ }
5607
+ function renderFeishuRunTimeline(presentation, options) {
5608
+ const visible = presentation.steps.filter(isVisibleStep);
5609
+ const elements = [];
5610
+ for (let index = 0; index < visible.length;) {
5611
+ const step = visible[index];
5612
+ if (step.kind !== "tool") {
5613
+ elements.push(...renderTimelineStep(step, options.expanded));
5614
+ index += 1;
5615
+ continue;
5616
+ }
5617
+ const tools = [];
5618
+ while (index < visible.length && visible[index]?.kind === "tool") {
5619
+ tools.push(visible[index]);
5620
+ index += 1;
5621
+ }
5622
+ elements.push(renderToolSequence(tools, options.expanded));
5623
+ }
5624
+ return {
5625
+ elements,
5626
+ inspectable: visible.some((step) => step.kind === "skill" || step.kind === "tool")
5627
+ };
5628
+ }
5629
+ function renderFeishuVisibleAnswer(presentation, currentText) {
5630
+ const committed = presentation?.steps.filter((step) => step.kind === "assistant").map((step) => step.text.trim()).filter(Boolean) ?? [];
5631
+ const current = currentText.trim();
5632
+ return [...committed, ...current ? [current] : []].join("\n\n");
5633
+ }
5634
+ function renderProgressSteps(steps) {
5635
+ const rendered = [];
5636
+ for (let index = 0; index < steps.length;) {
5637
+ const step = steps[index];
5638
+ if (step.kind !== "tool") {
5639
+ rendered.push(renderStep(step));
5640
+ index += 1;
5641
+ continue;
5642
+ }
5643
+ const tools = [];
5644
+ while (index < steps.length && steps[index]?.kind === "tool") {
5645
+ tools.push(steps[index]);
5646
+ index += 1;
5647
+ }
5648
+ rendered.push(tools.map(renderToolSummary).join("\n"));
5649
+ }
5650
+ return rendered;
5651
+ }
5652
+ function renderToolSequence(tools, expanded) {
5653
+ if (tools.length === 1) return {
5654
+ content: renderToolSummary(tools[0]),
5655
+ tag: "markdown"
5656
+ };
5657
+ return collapsiblePanel({
5658
+ content: tools.map(renderToolSummary).join("\n"),
5659
+ expanded: expanded || tools.some((tool) => tool.status === "running"),
5660
+ title: toolSequenceTitle(tools)
5661
+ });
5662
+ }
5663
+ function toolSequenceTitle(tools) {
5664
+ if ([...tools].reverse().find((tool) => tool.status === "running")) return `执行过程 · ${tools.length} 个工具 · 进行中`;
5665
+ if (tools.some((tool) => tool.status === "failed")) return `执行过程 · ${tools.length} 个工具 · 有失败`;
5666
+ const totalDuration = tools.reduce((total, tool) => total + (tool.durationMs ?? 0), 0);
5667
+ return totalDuration > 0 ? `执行过程 · ${tools.length} 个工具 · 已完成 · ${formatDuration$1(totalDuration)}` : `执行过程 · ${tools.length} 个工具 · 已完成`;
5668
+ }
5669
+ function renderToolSummary(step) {
5670
+ const skill = step.skillId ? ` · ${escapeInline(step.skillId)}` : "";
5671
+ const duration = step.durationMs === void 0 ? "" : ` · ${formatDuration$1(step.durationMs)}`;
5672
+ return `- ${statusIcon(step.status)} **${escapeInline(toolAction(step.toolName))}**${skill}${duration}`;
5673
+ }
5674
+ function renderTimelineStep(step, expandCompleted) {
5675
+ switch (step.kind) {
5676
+ case "assistant": return [{
5677
+ content: step.text,
5678
+ margin: "0px",
5679
+ tag: "markdown",
5680
+ text_align: "left",
5681
+ text_size: "normal"
5682
+ }];
5683
+ case "skill": return [collapsiblePanel({
5684
+ content: skillDetails(step),
5685
+ expanded: step.status === "running" || expandCompleted,
5686
+ title: `${step.skillId} · ${step.status === "running" ? "读取中" : step.status === "failed" ? "读取失败" : "已读取"}`
5687
+ })];
5688
+ case "tool": return [{
5689
+ content: renderToolSummary(step),
5690
+ tag: "markdown"
5691
+ }];
5692
+ case "model":
5693
+ case "response": return [];
5694
+ }
5695
+ }
5696
+ function collapsiblePanel(options) {
5697
+ return {
5698
+ background_color: options.expanded ? "blue-50" : "grey-50",
5699
+ elements: [{
5700
+ content: options.content,
5701
+ tag: "markdown",
5702
+ text_align: "left",
5703
+ text_size: "normal"
5704
+ }],
5705
+ expanded: options.expanded,
5706
+ header: {
5707
+ expanded_title: {
5708
+ content: options.title,
5709
+ tag: "plain_text",
5710
+ text_align: "left"
5711
+ },
5712
+ icon: {
5713
+ color: "grey",
5714
+ tag: "standard_icon",
5715
+ token: "down-small-ccm_outlined"
5716
+ },
5717
+ icon_expanded_angle: -180,
5718
+ icon_position: "right",
5719
+ padding: "8px 12px 8px 12px",
5720
+ title: {
5721
+ content: options.title,
5722
+ tag: "plain_text",
5723
+ text_align: "left"
5724
+ },
5725
+ width: "fill"
5726
+ },
5727
+ margin: "8px 0px 0px 0px",
5728
+ padding: "12px 12px 12px 12px",
5729
+ tag: "collapsible_panel"
5730
+ };
5731
+ }
5732
+ function skillDetails(step) {
5733
+ const duration = step.durationMs === void 0 ? "" : ` · ${formatDuration$1(step.durationMs)}`;
5734
+ return [`**${statusLabel(step.status)}**${duration}`, `这一步:读取并应用技能 \`${escapeInline(step.skillId)}\` 的任务说明。`].join("\n\n");
5735
+ }
5736
+ function statusLabel(status) {
5737
+ switch (status) {
5738
+ case "completed": return "已完成";
5739
+ case "failed": return "未完成";
5740
+ case "running": return "进行中";
5741
+ }
5742
+ }
5743
+ function toolAction(toolName) {
5744
+ const normalized = toolName.toLowerCase();
5745
+ const executable = normalized.split(/[\\/]/u).at(-1) ?? normalized;
5746
+ if (executable === "bash" || executable === "exec" || executable === "exec_command") return "执行命令";
5747
+ if (normalized.includes("read") && normalized.includes("file")) return "读取文件";
5748
+ if (normalized.includes("write") && normalized.includes("file")) return "写入文件";
5749
+ if (normalized.includes("search") || normalized === "rg") return "搜索内容";
5750
+ if (normalized.includes("fetch") || normalized.includes("http")) return "访问服务";
5751
+ return `调用 ${toolName.replace(/^.*[\\/]/u, "")}`;
5752
+ }
5753
+ function renderStep(step) {
5754
+ const duration = step.durationMs === void 0 ? "" : ` · ${formatDuration$1(step.durationMs)}`;
5755
+ switch (step.kind) {
5756
+ case "assistant": return step.text;
5757
+ case "model": return `- ${statusIcon(step.status)} ${step.label}${duration}`;
5758
+ case "skill": return `- ${statusIcon(step.status)} ${step.label} \`${escapeInline(step.skillId)}\`${duration}`;
5759
+ case "response": return `- ${statusIcon(step.status)} ${step.label}${duration}`;
5760
+ case "tool": return renderToolSummary(step);
5761
+ }
5762
+ }
5763
+ function isVisibleStep(step) {
5764
+ return step.kind === "assistant" || step.kind === "skill" || step.kind === "tool";
5765
+ }
5766
+ function runningLabel(step) {
5767
+ switch (step.kind) {
5768
+ case "assistant": return "正在说明进展";
5769
+ case "model": return "正在分析";
5770
+ case "skill": return "正在读取技能";
5771
+ case "tool": return `正在调用 ${step.toolName}`;
5772
+ case "response": return "正在整理回答";
5773
+ }
5774
+ }
5775
+ function statusIcon(status) {
5776
+ switch (status) {
5777
+ case "completed": return "✅";
5778
+ case "failed": return "⚠️";
5779
+ case "running": return "⏳";
5780
+ }
5781
+ }
5782
+ function formatDuration$1(milliseconds) {
5783
+ if (milliseconds < 1e3) return `${milliseconds} 毫秒`;
5784
+ if (milliseconds < 6e4) {
5785
+ const seconds = milliseconds / 1e3;
5786
+ return `${Number(seconds.toFixed(seconds < 10 ? 1 : 0))} 秒`;
5787
+ }
5788
+ return `${Math.floor(milliseconds / 6e4)} 分 ${Math.floor(milliseconds % 6e4 / 1e3)} 秒`;
5789
+ }
5790
+ function escapeInline(value) {
5791
+ return value.replace(/`/g, "ˋ");
5792
+ }
5793
+ //#endregion
5591
5794
  //#region src/adapters/feishu/card-presentation/rollover/feishu-card-rollover.ts
5592
5795
  function createFeishuCardRollover(options) {
5593
5796
  const leaseMs = validateCardPresentationLeaseMs(options.leaseMs ?? 51e4);
5797
+ const progressDisplay = options.progressDisplay ?? "collapsed";
5594
5798
  const counters = createCounters();
5595
5799
  const latestPresentationByRun = /* @__PURE__ */ new Map();
5596
5800
  const latestTextByRun = /* @__PURE__ */ new Map();
@@ -5598,6 +5802,7 @@ function createFeishuCardRollover(options) {
5598
5802
  const liveRuns = /* @__PURE__ */ new Map();
5599
5803
  const semaphore = Effect.unsafeMakeSemaphore(1);
5600
5804
  const exclusive = (effect) => semaphore.withPermits(1)(effect);
5805
+ const exclusiveDeferred = (factory) => semaphore.withPermits(1)(Effect.suspend(factory));
5601
5806
  const record = (event, at) => {
5602
5807
  counters[event.type] += 1;
5603
5808
  options.observe?.({
@@ -5606,36 +5811,86 @@ function createFeishuCardRollover(options) {
5606
5811
  });
5607
5812
  };
5608
5813
  const recordNow = (event) => options.clock.now.pipe(Effect.map((at) => record(event, at)));
5814
+ const failHandoffAndRecord = (input) => options.store.failHandoff({
5815
+ failedAt: input.failedAt,
5816
+ runId: input.runId
5817
+ }).pipe(Effect.catchAll((storeError) => recordNow({
5818
+ error: storeError,
5819
+ runId: input.runId,
5820
+ type: "presentation_write_failed"
5821
+ })), Effect.flatMap(() => recordNow({
5822
+ cardId: input.predecessor.cardId,
5823
+ error: input.error,
5824
+ generation: input.generation,
5825
+ presentationId: input.predecessor.presentationId,
5826
+ runId: input.runId,
5827
+ ...input.successorCardId === void 0 ? {} : { successorCardId: input.successorCardId },
5828
+ type: input.type
5829
+ })), Effect.as({ error: input.error }));
5609
5830
  const leaseDeadline = (at) => cardPresentationLeaseDeadline(at.toISOString(), leaseMs);
5610
5831
  const presentationId = (runId, generation) => `${runId}#${generation}`;
5611
- const publishProgress = (action) => Effect.suspend(() => {
5832
+ const publishProgress = (action) => Effect.gen(function* () {
5612
5833
  const chain = options.store.chain(action.runId);
5613
- if (chain && acceptsCardPresentationProgress(chain)) {
5614
- if (action.type === "update_text") latestTextByRun.set(action.runId, action.text);
5615
- else {
5616
- latestPresentationByRun.set(action.runId, action.presentation);
5617
- latestTextByRun.set(action.runId, action.presentation.answer.text);
5618
- }
5619
- const run = liveRuns.get(action.runId);
5620
- if (!run) return options.publisher.publish(action);
5621
- const baseline = generationBaselineByRun.get(action.runId);
5622
- const text = projectText(latestTextByRun.get(action.runId), baseline?.text);
5623
- const presentation = projectPresentation(latestPresentationByRun.get(action.runId), baseline);
5624
- return options.publisher.publish({
5625
- ...presentation === void 0 ? {} : { presentation },
5834
+ if (!chain) {
5835
+ yield* recordNow({
5626
5836
  runId: action.runId,
5627
- sessionKey: run.sessionKey,
5628
- ...text === void 0 ? {} : { text },
5629
- type: "update_presentation"
5837
+ type: "stale_update_dropped"
5630
5838
  });
5839
+ return;
5631
5840
  }
5632
- return recordNow({
5633
- ...chain ? {
5841
+ const previousPresentation = latestPresentationByRun.get(action.runId);
5842
+ const visibleChanged = hasVisibleIncrement(previousPresentation, latestTextByRun.get(action.runId), action.type === "update_progress" ? action.presentation : previousPresentation, action.type === "update_text" ? action.text : action.presentation.answer.text, progressDisplay);
5843
+ const run = liveRuns.get(action.runId);
5844
+ const acceptsProgress = acceptsCardPresentationProgress(chain);
5845
+ if (!acceptsProgress && !(run !== void 0)) {
5846
+ yield* recordNow({
5634
5847
  generation: chain.activeGeneration,
5635
- presentationId: chain.activePresentationId
5636
- } : {},
5848
+ presentationId: chain.activePresentationId,
5849
+ runId: action.runId,
5850
+ type: "stale_update_dropped"
5851
+ });
5852
+ return;
5853
+ }
5854
+ if (run && acceptsProgress) {
5855
+ const now = yield* options.clock.now;
5856
+ if (visibleChanged && isCardPresentationHandoffDue(chain, now.toISOString())) {
5857
+ if (yield* options.publisher.flush(action.runId).pipe(Effect.map(() => true), Effect.catchAll((error) => recordNow({
5858
+ error,
5859
+ runId: action.runId,
5860
+ type: "presentation_write_failed"
5861
+ }).pipe(Effect.as(false))))) yield* runHandoff(action.runId, true);
5862
+ }
5863
+ }
5864
+ if (action.type === "update_text") latestTextByRun.set(action.runId, action.text);
5865
+ else {
5866
+ latestPresentationByRun.set(action.runId, action.presentation);
5867
+ latestTextByRun.set(action.runId, action.presentation.answer.text);
5868
+ }
5869
+ const currentChain = options.store.chain(action.runId);
5870
+ if (!currentChain || !acceptsCardPresentationProgress(currentChain)) {
5871
+ yield* recordNow({
5872
+ ...currentChain ? {
5873
+ generation: currentChain.activeGeneration,
5874
+ presentationId: currentChain.activePresentationId
5875
+ } : {},
5876
+ runId: action.runId,
5877
+ type: "stale_update_dropped"
5878
+ });
5879
+ return;
5880
+ }
5881
+ if (!run) {
5882
+ yield* options.publisher.publish(action);
5883
+ return;
5884
+ }
5885
+ const baseline = generationBaselineByRun.get(action.runId);
5886
+ const text = projectText(latestTextByRun.get(action.runId), baseline?.text);
5887
+ const presentation = projectPresentation(latestPresentationByRun.get(action.runId), baseline);
5888
+ yield* options.publisher.publish({
5889
+ ...presentation === void 0 ? {} : { presentation },
5637
5890
  runId: action.runId,
5638
- type: "stale_update_dropped"
5891
+ sessionKey: run.sessionKey,
5892
+ ...text === void 0 ? {} : { text },
5893
+ type: "update_presentation"
5639
5894
  });
5640
5895
  });
5641
5896
  const publishTerminal = (action) => {
@@ -5675,7 +5930,7 @@ function createFeishuCardRollover(options) {
5675
5930
  default: return publishTerminal(action);
5676
5931
  }
5677
5932
  };
5678
- const runHandoff = (runId) => Effect.gen(function* () {
5933
+ const runHandoff = (runId, triggeredByVisibleProgress) => Effect.gen(function* () {
5679
5934
  const run = liveRuns.get(runId);
5680
5935
  if (!run) return {
5681
5936
  reason: "not-live",
@@ -5687,6 +5942,10 @@ function createFeishuCardRollover(options) {
5687
5942
  reason: "not-due",
5688
5943
  status: "skipped"
5689
5944
  };
5945
+ if (!triggeredByVisibleProgress) return {
5946
+ reason: "not-due",
5947
+ status: "skipped"
5948
+ };
5690
5949
  const start = yield* options.store.beginHandoff({
5691
5950
  handoffAt: startedAt.toISOString(),
5692
5951
  runId
@@ -5714,27 +5973,20 @@ function createFeishuCardRollover(options) {
5714
5973
  generation,
5715
5974
  presentationId: successorId,
5716
5975
  run
5717
- }).pipe(Effect.map((target) => ({ target })), Effect.catchAll((error) => options.store.failHandoff({
5718
- failedAt: startedAt.toISOString(),
5719
- runId
5720
- }).pipe(Effect.catchAll((storeError) => recordNow({
5721
- error: storeError,
5722
- runId,
5723
- type: "presentation_write_failed"
5724
- })), Effect.flatMap(() => recordNow({
5725
- cardId: predecessor.cardId,
5976
+ }).pipe(Effect.map((target) => ({ target })), Effect.catchAll((error) => failHandoffAndRecord({
5726
5977
  error,
5978
+ failedAt: startedAt.toISOString(),
5727
5979
  generation,
5728
- presentationId: predecessor.presentationId,
5980
+ predecessor,
5729
5981
  runId,
5730
5982
  type: "handoff_failed"
5731
- })), Effect.as({ error }))));
5983
+ })));
5732
5984
  if ("error" in created) return {
5733
5985
  error: created.error,
5734
5986
  status: "failed"
5735
5987
  };
5736
5988
  const adoptedAt = yield* options.clock.now;
5737
- const presentation = yield* options.store.completeHandoff({
5989
+ const completed = yield* options.store.completeHandoff({
5738
5990
  runId,
5739
5991
  successor: {
5740
5992
  cardId: created.target.cardId,
@@ -5743,7 +5995,20 @@ function createFeishuCardRollover(options) {
5743
5995
  leaseDeadlineAt: leaseDeadline(adoptedAt),
5744
5996
  presentationId: successorId
5745
5997
  }
5746
- });
5998
+ }).pipe(Effect.map((presentation) => ({ presentation })), Effect.catchAll((error) => failHandoffAndRecord({
5999
+ error,
6000
+ failedAt: adoptedAt.toISOString(),
6001
+ generation,
6002
+ predecessor,
6003
+ runId,
6004
+ successorCardId: created.target.cardId,
6005
+ type: "presentation_write_failed"
6006
+ })));
6007
+ if ("error" in completed) return {
6008
+ error: completed.error,
6009
+ status: "failed"
6010
+ };
6011
+ const presentation = completed.presentation;
5747
6012
  record({
5748
6013
  cardId: predecessor.cardId,
5749
6014
  generation,
@@ -5787,8 +6052,8 @@ function createFeishuCardRollover(options) {
5787
6052
  return chain !== void 0 && isCardPresentationHandoffDue(chain, nowIso);
5788
6053
  });
5789
6054
  },
5790
- flush: (runId) => exclusive(options.publisher.flush(runId)),
5791
- handoff: (runId) => exclusive(runHandoff(runId)),
6055
+ flush: (runId) => exclusiveDeferred(() => options.publisher.flush(runId)),
6056
+ handoff: (runId) => exclusive(runHandoff(runId, false)),
5792
6057
  observeStreamClosed: (cardId) => {
5793
6058
  counters.stream_closed += 1;
5794
6059
  options.observe?.({
@@ -5796,7 +6061,7 @@ function createFeishuCardRollover(options) {
5796
6061
  type: "stream_closed"
5797
6062
  });
5798
6063
  },
5799
- publish: (action) => exclusive(publishAction(action)),
6064
+ publish: (action) => exclusiveDeferred(() => publishAction(action)),
5800
6065
  releaseRun: (runId) => Effect.sync(() => {
5801
6066
  latestTextByRun.delete(runId);
5802
6067
  latestPresentationByRun.delete(runId);
@@ -5830,16 +6095,26 @@ function projectText(text, baseline) {
5830
6095
  if (text === baseline) return "";
5831
6096
  return text.startsWith(baseline) ? text.slice(baseline.length) : text;
5832
6097
  }
6098
+ function hasVisibleIncrement(previousPresentation, previousText, nextPresentation, nextText, progressDisplay) {
6099
+ const baseline = previousPresentation === void 0 && previousText === void 0 ? void 0 : {
6100
+ ...previousPresentation === void 0 ? {} : { presentation: previousPresentation },
6101
+ ...previousText === void 0 ? {} : { text: previousText }
6102
+ };
6103
+ const text = projectText(nextText, previousText) ?? "";
6104
+ const presentation = projectPresentation(nextPresentation, baseline);
6105
+ if (renderFeishuVisibleAnswer(presentation, text).trim().length > 0) return true;
6106
+ return progressDisplay !== "hidden" && (presentation?.steps.some((step) => step.kind === "skill" || step.kind === "tool") ?? false);
6107
+ }
5833
6108
  function projectPresentation(presentation, baseline) {
5834
6109
  if (!presentation || !baseline) return presentation;
5835
- const previous = new Map(baseline.presentation?.steps.map((step) => [step.id, JSON.stringify(step)]) ?? []);
6110
+ const previous = new Map(baseline.presentation?.steps.filter((step) => step.kind === "assistant" || step.kind === "skill" || step.kind === "tool").map((step) => [step.id, visibleStepSignature(step)]) ?? []);
5836
6111
  let skippedCommittedBaselineText = false;
5837
6112
  const steps = presentation.steps.filter((step) => {
5838
6113
  if (!skippedCommittedBaselineText && step.kind === "assistant" && baseline.text?.trim() && step.text.trim() === baseline.text.trim() && !previous.has(step.id)) {
5839
6114
  skippedCommittedBaselineText = true;
5840
6115
  return false;
5841
6116
  }
5842
- return previous.get(step.id) !== JSON.stringify(step);
6117
+ return (step.kind === "assistant" || step.kind === "skill" || step.kind === "tool") && previous.get(step.id) !== visibleStepSignature(step);
5843
6118
  });
5844
6119
  const answer = projectText(presentation.answer.text, baseline.text) ?? "";
5845
6120
  if (steps.length === 0 && answer.trim() === "") return void 0;
@@ -5855,6 +6130,25 @@ function projectPresentation(presentation, baseline) {
5855
6130
  totalToolCallCount: steps.filter((step) => step.kind === "tool").length
5856
6131
  };
5857
6132
  }
6133
+ function visibleStepSignature(step) {
6134
+ if (step.kind === "assistant") return JSON.stringify({
6135
+ id: step.id,
6136
+ kind: step.kind,
6137
+ text: step.text
6138
+ });
6139
+ return step.kind === "skill" ? JSON.stringify({
6140
+ id: step.id,
6141
+ kind: step.kind,
6142
+ skillId: step.skillId,
6143
+ status: step.status
6144
+ }) : JSON.stringify({
6145
+ id: step.id,
6146
+ kind: step.kind,
6147
+ skillId: step.skillId,
6148
+ status: step.status,
6149
+ toolName: step.toolName
6150
+ });
6151
+ }
5858
6152
  function projectTerminalAction(action, baseline) {
5859
6153
  if (!baseline || action.type !== "finish" && action.type !== "fail" && action.type !== "cancel") return action;
5860
6154
  const presentation = action.presentation ? projectPresentation(action.presentation, baseline) : void 0;
@@ -5923,20 +6217,20 @@ function createCoalescingFeishuPublisher(options) {
5923
6217
  const pendingProgressByRun = /* @__PURE__ */ new Map();
5924
6218
  const pendingPresentationByRun = /* @__PURE__ */ new Map();
5925
6219
  const flushText = (runId, text) => Effect.gen(function* () {
5926
- pendingTextByRun.delete(runId);
5927
6220
  yield* options.publisher.publish({
5928
6221
  runId,
5929
6222
  text,
5930
6223
  type: "update_text"
5931
6224
  });
6225
+ if (pendingTextByRun.get(runId) === text) pendingTextByRun.delete(runId);
5932
6226
  });
5933
6227
  const flushProgress = (runId, presentation) => Effect.gen(function* () {
5934
- pendingProgressByRun.delete(runId);
5935
6228
  yield* options.publisher.publish({
5936
6229
  presentation,
5937
6230
  runId,
5938
6231
  type: "update_progress"
5939
6232
  });
6233
+ if (pendingProgressByRun.get(runId) === presentation) pendingProgressByRun.delete(runId);
5940
6234
  });
5941
6235
  const flushRun = (runId) => {
5942
6236
  const text = pendingTextByRun.get(runId);
@@ -5944,10 +6238,10 @@ function createCoalescingFeishuPublisher(options) {
5944
6238
  const presentation = pendingPresentationByRun.get(runId);
5945
6239
  return Effect.gen(function* () {
5946
6240
  if (presentation !== void 0) {
5947
- pendingPresentationByRun.delete(runId);
5948
6241
  yield* options.publisher.publish(presentation);
5949
- pendingProgressByRun.delete(runId);
5950
- pendingTextByRun.delete(runId);
6242
+ if (pendingPresentationByRun.get(runId) === presentation) pendingPresentationByRun.delete(runId);
6243
+ if (pendingProgressByRun.get(runId) === progress) pendingProgressByRun.delete(runId);
6244
+ if (pendingTextByRun.get(runId) === text) pendingTextByRun.delete(runId);
5951
6245
  return;
5952
6246
  }
5953
6247
  if (progress !== void 0) yield* flushProgress(runId, progress);
@@ -6077,203 +6371,6 @@ function createConfiguredFeishuOpenApiClient(options) {
6077
6371
  });
6078
6372
  }
6079
6373
  //#endregion
6080
- //#region src/adapters/feishu/run-presentation/feishu-run-presentation-renderer.ts
6081
- function renderFeishuRunProgress(presentation) {
6082
- const visible = presentation.steps.filter(isVisibleStep);
6083
- const running = [...visible].reverse().find((step) => step.status === "running");
6084
- const title = visible.length === 0 ? "执行过程 · 展开查看" : presentation.phase === "running" && running ? `执行过程 · ${runningLabel(running)}` : `执行过程 · ${visible.length} 段 · ${presentation.totalToolCallCount} 次工具调用`;
6085
- return {
6086
- markdown: [...presentation.omittedStepCount > 0 ? [`> 另有 ${presentation.omittedStepCount} 个较早步骤未展示`] : [], ...renderProgressSteps(visible)].join("\n\n") || "正在准备执行…",
6087
- title
6088
- };
6089
- }
6090
- function renderFeishuRunTimeline(presentation, options) {
6091
- const visible = presentation.steps.filter(isVisibleStep);
6092
- const elements = [];
6093
- for (let index = 0; index < visible.length;) {
6094
- const step = visible[index];
6095
- if (step.kind !== "tool") {
6096
- elements.push(...renderTimelineStep(step, options.expanded));
6097
- index += 1;
6098
- continue;
6099
- }
6100
- const tools = [];
6101
- while (index < visible.length && visible[index]?.kind === "tool") {
6102
- tools.push(visible[index]);
6103
- index += 1;
6104
- }
6105
- elements.push(renderToolSequence(tools, options.expanded));
6106
- }
6107
- return {
6108
- elements,
6109
- inspectable: visible.some((step) => step.kind === "skill" || step.kind === "tool")
6110
- };
6111
- }
6112
- function renderFeishuVisibleAnswer(presentation, currentText) {
6113
- const committed = presentation?.steps.filter((step) => step.kind === "assistant").map((step) => step.text.trim()).filter(Boolean) ?? [];
6114
- const current = currentText.trim();
6115
- return [...committed, ...current ? [current] : []].join("\n\n");
6116
- }
6117
- function renderProgressSteps(steps) {
6118
- const rendered = [];
6119
- for (let index = 0; index < steps.length;) {
6120
- const step = steps[index];
6121
- if (step.kind !== "tool") {
6122
- rendered.push(renderStep(step));
6123
- index += 1;
6124
- continue;
6125
- }
6126
- const tools = [];
6127
- while (index < steps.length && steps[index]?.kind === "tool") {
6128
- tools.push(steps[index]);
6129
- index += 1;
6130
- }
6131
- rendered.push(tools.map(renderToolSummary).join("\n"));
6132
- }
6133
- return rendered;
6134
- }
6135
- function renderToolSequence(tools, expanded) {
6136
- if (tools.length === 1) return {
6137
- content: renderToolSummary(tools[0]),
6138
- tag: "markdown"
6139
- };
6140
- return collapsiblePanel({
6141
- content: tools.map(renderToolSummary).join("\n"),
6142
- expanded: expanded || tools.some((tool) => tool.status === "running"),
6143
- title: toolSequenceTitle(tools)
6144
- });
6145
- }
6146
- function toolSequenceTitle(tools) {
6147
- if ([...tools].reverse().find((tool) => tool.status === "running")) return `执行过程 · ${tools.length} 个工具 · 进行中`;
6148
- if (tools.some((tool) => tool.status === "failed")) return `执行过程 · ${tools.length} 个工具 · 有失败`;
6149
- const totalDuration = tools.reduce((total, tool) => total + (tool.durationMs ?? 0), 0);
6150
- return totalDuration > 0 ? `执行过程 · ${tools.length} 个工具 · 已完成 · ${formatDuration$1(totalDuration)}` : `执行过程 · ${tools.length} 个工具 · 已完成`;
6151
- }
6152
- function renderToolSummary(step) {
6153
- const skill = step.skillId ? ` · ${escapeInline(step.skillId)}` : "";
6154
- const duration = step.durationMs === void 0 ? "" : ` · ${formatDuration$1(step.durationMs)}`;
6155
- return `- ${statusIcon(step.status)} **${escapeInline(toolAction(step.toolName))}**${skill}${duration}`;
6156
- }
6157
- function renderTimelineStep(step, expandCompleted) {
6158
- switch (step.kind) {
6159
- case "assistant": return [{
6160
- content: step.text,
6161
- margin: "0px",
6162
- tag: "markdown",
6163
- text_align: "left",
6164
- text_size: "normal"
6165
- }];
6166
- case "skill": return [collapsiblePanel({
6167
- content: skillDetails(step),
6168
- expanded: step.status === "running" || expandCompleted,
6169
- title: `${step.skillId} · ${step.status === "running" ? "读取中" : step.status === "failed" ? "读取失败" : "已读取"}`
6170
- })];
6171
- case "tool": return [{
6172
- content: renderToolSummary(step),
6173
- tag: "markdown"
6174
- }];
6175
- case "model":
6176
- case "response": return [];
6177
- }
6178
- }
6179
- function collapsiblePanel(options) {
6180
- return {
6181
- background_color: options.expanded ? "blue-50" : "grey-50",
6182
- elements: [{
6183
- content: options.content,
6184
- tag: "markdown",
6185
- text_align: "left",
6186
- text_size: "normal"
6187
- }],
6188
- expanded: options.expanded,
6189
- header: {
6190
- expanded_title: {
6191
- content: options.title,
6192
- tag: "plain_text",
6193
- text_align: "left"
6194
- },
6195
- icon: {
6196
- color: "grey",
6197
- tag: "standard_icon",
6198
- token: "down-small-ccm_outlined"
6199
- },
6200
- icon_expanded_angle: -180,
6201
- icon_position: "right",
6202
- padding: "8px 12px 8px 12px",
6203
- title: {
6204
- content: options.title,
6205
- tag: "plain_text",
6206
- text_align: "left"
6207
- },
6208
- width: "fill"
6209
- },
6210
- margin: "8px 0px 0px 0px",
6211
- padding: "12px 12px 12px 12px",
6212
- tag: "collapsible_panel"
6213
- };
6214
- }
6215
- function skillDetails(step) {
6216
- const duration = step.durationMs === void 0 ? "" : ` · ${formatDuration$1(step.durationMs)}`;
6217
- return [`**${statusLabel(step.status)}**${duration}`, `这一步:读取并应用技能 \`${escapeInline(step.skillId)}\` 的任务说明。`].join("\n\n");
6218
- }
6219
- function statusLabel(status) {
6220
- switch (status) {
6221
- case "completed": return "已完成";
6222
- case "failed": return "未完成";
6223
- case "running": return "进行中";
6224
- }
6225
- }
6226
- function toolAction(toolName) {
6227
- const normalized = toolName.toLowerCase();
6228
- const executable = normalized.split(/[\\/]/u).at(-1) ?? normalized;
6229
- if (executable === "bash" || executable === "exec" || executable === "exec_command") return "执行命令";
6230
- if (normalized.includes("read") && normalized.includes("file")) return "读取文件";
6231
- if (normalized.includes("write") && normalized.includes("file")) return "写入文件";
6232
- if (normalized.includes("search") || normalized === "rg") return "搜索内容";
6233
- if (normalized.includes("fetch") || normalized.includes("http")) return "访问服务";
6234
- return `调用 ${toolName.replace(/^.*[\\/]/u, "")}`;
6235
- }
6236
- function renderStep(step) {
6237
- const duration = step.durationMs === void 0 ? "" : ` · ${formatDuration$1(step.durationMs)}`;
6238
- switch (step.kind) {
6239
- case "assistant": return step.text;
6240
- case "model": return `- ${statusIcon(step.status)} ${step.label}${duration}`;
6241
- case "skill": return `- ${statusIcon(step.status)} ${step.label} \`${escapeInline(step.skillId)}\`${duration}`;
6242
- case "response": return `- ${statusIcon(step.status)} ${step.label}${duration}`;
6243
- case "tool": return renderToolSummary(step);
6244
- }
6245
- }
6246
- function isVisibleStep(step) {
6247
- return step.kind === "assistant" || step.kind === "skill" || step.kind === "tool";
6248
- }
6249
- function runningLabel(step) {
6250
- switch (step.kind) {
6251
- case "assistant": return "正在说明进展";
6252
- case "model": return "正在分析";
6253
- case "skill": return "正在读取技能";
6254
- case "tool": return `正在调用 ${step.toolName}`;
6255
- case "response": return "正在整理回答";
6256
- }
6257
- }
6258
- function statusIcon(status) {
6259
- switch (status) {
6260
- case "completed": return "✅";
6261
- case "failed": return "⚠️";
6262
- case "running": return "⏳";
6263
- }
6264
- }
6265
- function formatDuration$1(milliseconds) {
6266
- if (milliseconds < 1e3) return `${milliseconds} 毫秒`;
6267
- if (milliseconds < 6e4) {
6268
- const seconds = milliseconds / 1e3;
6269
- return `${Number(seconds.toFixed(seconds < 10 ? 1 : 0))} 秒`;
6270
- }
6271
- return `${Math.floor(milliseconds / 6e4)} 分 ${Math.floor(milliseconds % 6e4 / 1e3)} 秒`;
6272
- }
6273
- function escapeInline(value) {
6274
- return value.replace(/`/g, "ˋ");
6275
- }
6276
- //#endregion
6277
6374
  //#region src/adapters/feishu/run-presentation/feishu-agent-run-card.ts
6278
6375
  const FEISHU_AGENT_CARD_ELEMENT_ID = "rivus_agent_answer";
6279
6376
  const FEISHU_AGENT_CARD_PROGRESS_ELEMENT_ID = "rivus_agent_progress";
@@ -6704,6 +6801,7 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
6704
6801
  }),
6705
6802
  leaseMs,
6706
6803
  ...options.observe ? { observe: options.observe } : {},
6804
+ progressDisplay,
6707
6805
  publisher,
6708
6806
  store: options.cardTargets
6709
6807
  });
@@ -6718,7 +6816,7 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
6718
6816
  const streaming = createPeriodicEffectLoop({
6719
6817
  intervalMs: options.flushIntervalMs ?? options.config.feishu.streamMinIntervalMs,
6720
6818
  ...options.onError ? { onError: options.onError } : {},
6721
- run: () => publisher.flush(),
6819
+ run: () => cardRollover.flush(),
6722
6820
  sleep: options.sleep
6723
6821
  });
6724
6822
  return {
@@ -7654,22 +7752,114 @@ function resolveContentMode(value) {
7654
7752
  throw new LangfuseTelemetryConfigError("RIVUS_TELEMETRY_CONTENT must be either \"metadata-only\" or \"redacted\"");
7655
7753
  }
7656
7754
  //#endregion
7755
+ //#region src/adapters/pi/execution/pi-session-prompts.ts
7756
+ /** One ordinary prompt writer, with ordered native steering while that prompt is streaming. */
7757
+ async function runPiSessionPrompts(options) {
7758
+ const { input, handle } = options;
7759
+ const { session } = handle;
7760
+ const prepare = (text) => handle.preparePrompt?.({
7761
+ ...input,
7762
+ text
7763
+ }) ?? text;
7764
+ if (!options.supportsSteering) {
7765
+ const text = await prepare(input.text);
7766
+ if (!input.abortSignal.aborted) await session.prompt(text);
7767
+ return;
7768
+ }
7769
+ if (!session.steer || typeof session.isStreaming !== "boolean" || !session.clearQueue) throw new Error("Pi steering requires native steer, isStreaming and clearQueue capabilities");
7770
+ const mailbox = input.steering;
7771
+ const stop = new AbortController();
7772
+ const onAbort = () => stop.abort();
7773
+ input.abortSignal.addEventListener("abort", onAbort, { once: true });
7774
+ if (input.abortSignal.aborted) stop.abort();
7775
+ const close = () => Effect.runPromise(mailbox?.close?.() ?? Effect.succeed([]));
7776
+ const take = () => Effect.runPromise(mailbox?.next() ?? Effect.never, { signal: stop.signal }).then((text) => ({
7777
+ type: "steering",
7778
+ text
7779
+ }), (error) => {
7780
+ if (stop.signal.aborted) return { type: "closed" };
7781
+ throw error;
7782
+ });
7783
+ let prompt = Promise.resolve({ type: "completed" });
7784
+ const startPrompt = (text) => {
7785
+ prompt = session.prompt(text).then(() => ({ type: "completed" }), (error) => ({
7786
+ type: "failed",
7787
+ error
7788
+ }));
7789
+ };
7790
+ const settlePrompt = async () => {
7791
+ const outcome = await prompt;
7792
+ if (outcome.type === "failed") throw outcome.error;
7793
+ };
7794
+ let next;
7795
+ let succeeded = false;
7796
+ try {
7797
+ const text = await prepare(input.text);
7798
+ if (input.abortSignal.aborted) return;
7799
+ startPrompt(text);
7800
+ next = take();
7801
+ while (!input.abortSignal.aborted) {
7802
+ const outcome = await Promise.race([next, prompt]);
7803
+ if (outcome.type === "failed") throw outcome.error;
7804
+ if (outcome.type === "closed") break;
7805
+ if (outcome.type === "completed") {
7806
+ const remaining = await close();
7807
+ stop.abort();
7808
+ const claimed = await next;
7809
+ const accepted = claimed.type === "steering" ? [claimed.text, ...remaining] : remaining;
7810
+ for (const acceptedText of accepted) {
7811
+ const prepared = await prepare(acceptedText);
7812
+ if (input.abortSignal.aborted) return;
7813
+ startPrompt(prepared);
7814
+ await settlePrompt();
7815
+ }
7816
+ succeeded = true;
7817
+ return;
7818
+ }
7819
+ const prepared = await prepare(outcome.text);
7820
+ if (input.abortSignal.aborted) break;
7821
+ if (session.isStreaming) await session.steer(prepared);
7822
+ else {
7823
+ await settlePrompt();
7824
+ if (input.abortSignal.aborted) break;
7825
+ startPrompt(prepared);
7826
+ }
7827
+ next = take();
7828
+ }
7829
+ } finally {
7830
+ input.abortSignal.removeEventListener("abort", onAbort);
7831
+ await close();
7832
+ stop.abort();
7833
+ await next;
7834
+ const cancelledOrFailed = !succeeded || input.abortSignal.aborted;
7835
+ if (cancelledOrFailed) await options.abort();
7836
+ await prompt;
7837
+ if (cancelledOrFailed) session.clearQueue();
7838
+ }
7839
+ }
7840
+ //#endregion
7657
7841
  //#region src/adapters/pi/execution/pi-agent-loop.ts
7658
7842
  function createPiAgentLoop$1(options) {
7659
- return { run: (input) => {
7660
- const stream = Stream.fromAsyncIterable(runPiSession(input, options), (error) => error);
7661
- if (!options.runBoundary) return stream;
7662
- return Stream.unwrapScoped(Effect.acquireRelease(options.runBoundary.acquireRun({ signal: input.abortSignal }), (lease) => Effect.sync(lease.release)).pipe(Effect.as(stream)));
7663
- } };
7843
+ return {
7844
+ ...options.supportsSteering ? { supportsSteering: true } : {},
7845
+ run: (input) => {
7846
+ const stream = Stream.fromAsyncIterable(runPiSession(input, options), (error) => error);
7847
+ if (!options.runBoundary) return stream;
7848
+ return Stream.unwrapScoped(Effect.acquireRelease(options.runBoundary.acquireRun({ signal: input.abortSignal }), (lease) => Effect.sync(lease.release)).pipe(Effect.as(stream)));
7849
+ }
7850
+ };
7664
7851
  }
7665
7852
  function createPiSdkAgentLoop$1(options) {
7666
- return createPiAgentLoop$1({ resolveSession: async () => {
7667
- const result = await options.createAgentSession(options.sessionOptions);
7668
- return {
7669
- dispose: () => result.session.dispose?.(),
7670
- session: result.session
7671
- };
7672
- } });
7853
+ return createPiAgentLoop$1({
7854
+ ...options.supportsSteering ? { supportsSteering: true } : {},
7855
+ resolveSession: async () => {
7856
+ const result = await options.createAgentSession(options.sessionOptions);
7857
+ return {
7858
+ dispose: () => result.session.dispose?.(),
7859
+ session: result.session
7860
+ };
7861
+ }
7862
+ });
7673
7863
  }
7674
7864
  async function* runPiSession(input, options) {
7675
7865
  const queue = createAsyncQueue();
@@ -7726,7 +7916,12 @@ async function* runPiSession(input, options) {
7726
7916
  queue.offer(mapped);
7727
7917
  }
7728
7918
  });
7729
- await handle.session.prompt(handle.preparePrompt ? await handle.preparePrompt(input) : input.text);
7919
+ await runPiSessionPrompts({
7920
+ input,
7921
+ handle,
7922
+ supportsSteering: options.supportsSteering === true,
7923
+ abort: abortSession
7924
+ });
7730
7925
  complete(input.abortSignal.aborted || terminalErrorMessage === void 0 ? void 0 : new Error(terminalErrorMessage));
7731
7926
  } catch (error) {
7732
7927
  complete(input.abortSignal.aborted ? void 0 : error);
@@ -7986,6 +8181,7 @@ function createAsyncQueue() {
7986
8181
  //#region src/adapters/compatibility/agent-execution/pi/pi-agent-loop.ts
7987
8182
  function createPiAgentLoop(options) {
7988
8183
  return fromEffectAgentLoop(createPiAgentLoop$1({
8184
+ ...options.supportsSteering ? { supportsSteering: true } : {},
7989
8185
  ...options.disposeSessionAfterRun === void 0 ? {} : { disposeSessionAfterRun: options.disposeSessionAfterRun },
7990
8186
  ...options.modelContentObserver ? { modelContentObserver: options.modelContentObserver } : {},
7991
8187
  ...options.runBoundary ? { runBoundary: options.runBoundary } : {},