@roll-agent/runtime 0.3.0 → 0.4.0

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.
@@ -1,5 +1,6 @@
1
1
  import { type ModelMessage } from "ai";
2
2
  import type { LanguageModelV4, SharedV4ProviderOptions } from "@ai-sdk/provider";
3
+ import type { SkillLibrary, SkillSummary } from "@roll-agent/core/skills/library";
3
4
  import type { ContextCompactionReason, ContextCompactionStrategy, SessionEvent, SessionTokenUsage } from "../types/events.ts";
4
5
  import type { ToolPolicy } from "../types/policy.ts";
5
6
  import { type AgentToolSource } from "../tool-bridge/build-tools.ts";
@@ -24,7 +25,10 @@ export interface AgentSessionOptions {
24
25
  readonly turnTimeoutMs?: number;
25
26
  readonly providerOptions?: SharedV4ProviderOptions;
26
27
  readonly debugEvents?: boolean;
28
+ readonly systemPrompt?: string;
29
+ readonly skillLibrary?: SkillLibrary;
27
30
  }
31
+ export type SessionSkillSummary = SkillSummary;
28
32
  export declare class AgentSession {
29
33
  readonly id: string;
30
34
  private readonly model;
@@ -37,6 +41,8 @@ export declare class AgentSession {
37
41
  private readonly turnTimeoutMs;
38
42
  private providerOptions;
39
43
  private readonly debugEvents;
44
+ private readonly systemPrompt;
45
+ private readonly skillSummaries;
40
46
  private readonly gate;
41
47
  private readonly tools;
42
48
  private readonly registry;
@@ -55,6 +61,7 @@ export declare class AgentSession {
55
61
  getMessages(): readonly ModelMessage[];
56
62
  getContextWindow(): number | undefined;
57
63
  getSessionUsage(): SessionTokenUsage;
64
+ getSkillSummaries(): readonly SessionSkillSummary[];
58
65
  setProviderOptions(providerOptions: SharedV4ProviderOptions | undefined): void;
59
66
  private shouldAutoCompact;
60
67
  private runCompactionTurn;
@@ -1 +1 @@
1
- import{randomUUID as e}from"node:crypto";import{stepCountIs as t,streamText as s}from"ai";import{buildAgentToolset as o}from"../tool-bridge/build-tools.js";import{readIsError as n}from"../tool-bridge/normalize-result.js";import{ApprovalGate as i}from"../approval/approval-gate.js";import{compactMessages as r}from"./compactor.js";import{AsyncEventQueue as a}from"./event-queue.js";const u="你是 Roll chat 的运行时助手。你可以使用模型的 thinking/reasoning 能力完成内部推理,但必须把给用户看的最终回复写入普通 text 输出通道;不要只在 reasoning 中写最终答案。 工具调用完成后,也要在普通 text 输出通道给出简洁结论。最终回复不要重复。 不要复述用户输入;如果需要调用工具,直接调用工具,不要先输出用户原文或无意义前置文本。";function l(e){return e instanceof Error?e.message:String(e)}function p(e){return"object"==typeof e&&null!==e&&"output"in e}function h(e){return p(e)?l(e.output):l(e)}function c(e){const t=h(e);return t.startsWith("已取消执行")||t.startsWith("策略拒绝执行")?t:void 0}function d(e){return/context[_ -]?length|context window|maximum context|token limit|too many tokens|prompt is too long|input is too long/i.test(l(e))}function m(e){const t=e.inputTokenDetails?.cacheReadTokens,s=e.inputTokenDetails?.cacheWriteTokens,o=e.outputTokenDetails?.reasoningTokens;return{...void 0!==e.inputTokens?{inputTokens:e.inputTokens}:{},...void 0!==e.outputTokens?{outputTokens:e.outputTokens}:{},...void 0!==e.totalTokens?{totalTokens:e.totalTokens}:{},...void 0!==t?{cachedInputTokens:t}:{},...void 0!==s?{cacheWriteTokens:s}:{},...void 0!==o?{reasoningTokens:o}:{}}}function g(e,t){return void 0===e&&void 0===t?void 0:(e??0)+(t??0)}function v(e,t){const s=g(e.cachedInputTokens,t.cachedInputTokens),o=g(e.cacheWriteTokens,t.cacheWriteTokens),n=g(e.reasoningTokens,t.reasoningTokens);return{inputTokens:(e.inputTokens??0)+(t.inputTokens??0),outputTokens:(e.outputTokens??0)+(t.outputTokens??0),totalTokens:(e.totalTokens??0)+(t.totalTokens??0),...void 0!==s?{cachedInputTokens:s}:{},...void 0!==o?{cacheWriteTokens:o}:{},...void 0!==n?{reasoningTokens:n}:{}}}function T(e,t){return void 0===t?e:void 0===e?t:Math.max(e,t)}function b(e,t){const s=e.trim(),o=t.trim();return s.length>0&&o.length>0&&o.startsWith(s)}function k(e){return e.flatMap(e=>{if("assistant"!==e.role||!Array.isArray(e.content))return[e];const t=e.content.filter(e=>"reasoning"!==e.type);return 0===t.length?[]:t.length===e.content.length?[e]:[{...e,content:t}]})}export class AgentSession{id;model;maxSteps;messages;onPersist;onReplace;contextWindow;compaction;turnTimeoutMs;providerOptions;debugEvents;gate=new i;tools;registry;emit;activeTurn;sessionUsage={inputTokens:0,outputTokens:0,totalTokens:0};lastInputTokens;needsCompaction=!1;constructor(e){this.id=e.id,this.model=e.model,this.maxSteps=e.maxSteps,this.messages=e.initialMessages?k(e.initialMessages):[],this.onPersist=e.onPersist,this.onReplace=e.onReplace,this.contextWindow=e.contextWindow,this.compaction=e.compaction,this.turnTimeoutMs=e.turnTimeoutMs,this.providerOptions=e.providerOptions,this.debugEvents=e.debugEvents??!1;const t=o(e.sources,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)});this.tools=t.tools,this.registry=t.registry}async*send(e){if(this.activeTurn)throw new Error("session already has an active turn");const t=new a;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1};this.activeTurn=s,this.runTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"execute",message:l(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async*compact(e="manual"){if(this.activeTurn)throw new Error("session already has an active turn");const t=new a;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1};this.activeTurn=s,this.runCompactionTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"plan",message:l(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async runTurn(o,i,r){const a=Date.now();let p;try{if(this.debug(o,"turn","start",a,{messages:this.messages.length,tools:Object.keys(this.tools).length,maxSteps:this.maxSteps,...void 0!==this.contextWindow?{contextWindow:this.contextWindow}:{},...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}}),this.shouldAutoCompact()){this.debug(o,"compaction","auto requested before turn",a,{messages:this.messages.length,...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}});try{await this.runCompaction(o,"auto",i)}catch(e){return void o.push({type:"error",stage:"plan",message:l(e)})}}if(i.aborted||i.abortController.signal.aborted)return void o.push({type:"error",stage:"execute",message:"aborted"});p=this.messages.length,this.messages.push({role:"user",content:r}),o.push({type:"message-start",messageId:e()}),this.debug(o,"model","calling streamText",a,{messages:this.messages.length,tools:Object.keys(this.tools).length,...void 0!==this.turnTimeoutMs?{timeoutMs:this.turnTimeoutMs}:{}});const g=s({model:this.model,system:u,messages:this.messages,tools:this.tools,stopWhen:t(this.maxSteps),abortSignal:i.abortController.signal,...this.providerOptions?{providerOptions:this.providerOptions}:{},...void 0!==this.turnTimeoutMs?{timeout:{totalMs:this.turnTimeoutMs}}:{}});this.debug(o,"model","streamText returned",a);let y,f,x,w,C,I,N="",M="",S=!1,W=0,R=!1;const A=this.scheduleDebug(o,"model","waiting for first stream event",a,{messages:this.messages.length});try{for await(const e of g.fullStream){switch(R||(R=!0,this.clearDebugTimer(A),this.debug(o,"model","first stream event",a,{part:e.type})),e.type){case"text-delta":if(!S){const t=M+e.text;if(b(t,r)){M=t;break}if(M.length>0){const t=M+e.text;M="",N+=t,o.push({type:"text-delta",delta:t});break}}N+=e.text,o.push({type:"text-delta",delta:e.text});break;case"tool-call":{S=!0,M="";const t=this.registry.resolve(e.toolName);o.push({type:"tool-call",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,input:e.input});break}case"tool-result":{const t=this.registry.resolve(e.toolName),s=n(e.output);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:e.output,isError:s}),i.aborted||i.abortController.signal.aborted||!s||(I=c(e.output));break}case"tool-error":{const t=this.registry.resolve(e.toolName),s=h(e.error);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:s,isError:!0}),i.aborted||i.abortController.signal.aborted||(I=c(e.error));break}case"finish-step":{const t=m(e.usage);f=T(f,t.inputTokens),W+=1,w=e.finishReason;const s=e.performance.outputTokensPerSecond??e.performance.effectiveOutputTokensPerSecond;void 0!==s&&Number.isFinite(s)&&(x=s),o.push({type:"step-finish",finishReason:e.finishReason,usage:t});break}case"finish":!S&&M.length>0&&(N+=M,o.push({type:"text-delta",delta:M}),M=""),y=m(e.totalUsage);break;case"error":d(e.error)&&(this.needsCompaction=!0),C=l(e.error),o.push({type:"error",stage:"execute",message:C});break;case"abort":i.aborted=!0}if(void 0!==I)break}}finally{this.clearDebugTimer(A)}if(this.debug(o,"model","fullStream finished",a,{textChars:N.length}),void 0!==C)return this.messages.splice(p),void(this.needsCompaction&&await this.recoverFromContextError(o,i));if(i.aborted||i.abortController.signal.aborted)return this.messages.splice(p),void o.push({type:"error",stage:"execute",message:"aborted"});if(void 0!==I){const e=I,t=0===N.length?e:`\n${e}`;o.push({type:"text-delta",delta:t}),this.messages.push({role:"assistant",content:e}),this.debug(o,"persist","persisting messages",a,{appendedMessages:this.messages.length-p}),this.onPersist?.(this.messages.slice(p)),this.debug(o,"persist","messages persisted",a,{totalMessages:this.messages.length}),y&&(this.sessionUsage=v(this.sessionUsage,y));const s=f??y?.inputTokens;return void 0!==s&&(this.lastInputTokens=s),void o.push({type:"message-finish",text:e,...y?{totalUsage:y}:{},sessionUsage:{...this.sessionUsage},...void 0!==f?{contextInputTokens:f}:{},...void 0!==x?{outputTokensPerSecond:x}:{}})}let D;this.debug(o,"model","awaiting response messages",a);const E=this.scheduleDebug(o,"model","still awaiting response messages",a);try{D=(await g.response).messages}catch(e){return this.clearDebugTimer(E),d(e)&&(this.needsCompaction=!0),this.messages.splice(p),o.push({type:"error",stage:"execute",message:l(e)}),void(this.needsCompaction&&await this.recoverFromContextError(o,i))}finally{this.clearDebugTimer(E)}this.debug(o,"model","response messages ready",a,{responseMessages:D.length});const U=k(D);this.messages.push(...U),this.debug(o,"persist","persisting messages",a,{appendedMessages:this.messages.length-p}),this.onPersist?.(this.messages.slice(p)),this.debug(o,"persist","messages persisted",a,{totalMessages:this.messages.length}),y&&(this.sessionUsage=v(this.sessionUsage,y));const O=f??y?.inputTokens;void 0!==O&&(this.lastInputTokens=O);const P=W>=this.maxSteps&&"tool-calls"===w;o.push({type:"message-finish",text:N,...y?{totalUsage:y}:{},sessionUsage:{...this.sessionUsage},...void 0!==f?{contextInputTokens:f}:{},...void 0!==x?{outputTokensPerSecond:x}:{},...P?{stoppedAtStepLimit:!0}:{}})}catch(e){void 0!==p&&this.messages.splice(p),d(e)&&(this.needsCompaction=!0),this.debug(o,"turn","error",a,{message:l(e)}),o.push({type:"error",stage:"execute",message:l(e)}),this.needsCompaction&&await this.recoverFromContextError(o,i)}finally{this.activeTurn===i&&(this.activeTurn=void 0),o.close()}}requestApproval(t){const s=e(),o=this.gate.request(s);return void 0===this.emit?(this.gate.resolve(s,{approved:!1,reason:"approval event could not be delivered"}),o):(this.emit({type:"confirmation-required",approvalId:s,agentName:t.agentName,toolName:t.toolName,input:t.input,...t.reason?{reason:t.reason}:{}}),o)}approve(e){return this.gate.resolve(e,{approved:!0})}reject(e,t){return this.gate.resolve(e,{approved:!1,...t?{reason:t}:{}})}getMessages(){return this.messages}getContextWindow(){return this.contextWindow}getSessionUsage(){return{...this.sessionUsage}}setProviderOptions(e){this.providerOptions=e}shouldAutoCompact(){const e=this.compaction;return void 0!==e&&e.enabled&&(this.needsCompaction||void 0!==this.contextWindow&&void 0!==this.lastInputTokens&&this.lastInputTokens/this.contextWindow>=e.threshold)}async runCompactionTurn(e,t,s){try{await this.runCompaction(e,s,t)}catch(t){e.push({type:"error",stage:"plan",message:l(t)})}finally{this.activeTurn===t&&(this.activeTurn=void 0),e.close()}}async runCompaction(e,t,s){const o=Date.now(),n=this.compaction,i=n?.strategy??"summarize";if(this.debug(e,"compaction","start",o,{reason:t,messages:this.messages.length}),e.push({type:"compaction-start",reason:t}),this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});if(!n)return void e.push({type:"context-compacted",reason:t,strategy:i,removed:0,kept:this.messages.length});const a=this.lastInputTokens;let u=n.strategy;const p=s?.abortController.signal;let h;try{h=await r({messages:this.messages,strategy:u,keepRecentTurns:n.keepRecentTurns,keepRecentTokens:n.keepRecentTokens,model:this.model,...p?{abortSignal:p}:{}})}catch(t){if(this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});if("summarize"!==u)throw t;this.debug(e,"compaction","summarize failed, fallback to truncate",o,{message:l(t)}),u="truncate",h=await r({messages:this.messages,strategy:u,keepRecentTurns:n.keepRecentTurns,keepRecentTokens:n.keepRecentTokens,model:this.model,...p?{abortSignal:p}:{}})}if(this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});const c=h.removed>0||h.truncatedTools>0;c&&(this.onReplace?.(h.messages),this.messages.splice(0,this.messages.length,...h.messages),this.lastInputTokens=void 0,this.needsCompaction=!1),e.push({type:"context-compacted",reason:t,strategy:u,removed:h.removed,kept:h.kept,...h.truncatedTools>0?{truncatedTools:h.truncatedTools}:{},...void 0!==a?{beforeInputTokens:a}:{}}),this.debug(e,"compaction","finish",o,{reason:t,strategy:u,removed:h.removed,kept:h.kept,truncatedTools:h.truncatedTools,progressed:c})}async recoverFromContextError(e,t){if(this.compaction?.enabled)try{await this.runCompaction(e,"auto",t)}catch(t){e.push({type:"error",stage:"plan",message:l(t)})}}isTurnAborted(e){return!0===e?.aborted||!0===e?.abortController.signal.aborted}abort(){this.activeTurn&&(this.activeTurn.aborted=!0),this.gate.abortAll(),this.activeTurn?.abortController.abort()}debug(e,t,s,o,n){this.debugEvents&&e.push({type:"debug",stage:t,message:s,...void 0!==o?{elapsedMs:Date.now()-o}:{},...void 0!==n?{data:n}:{}})}scheduleDebug(e,t,s,o,n){if(this.debugEvents)return setTimeout(()=>{this.debug(e,t,s,o,n)},5e3)}clearDebugTimer(e){void 0!==e&&clearTimeout(e)}}
1
+ import{randomUUID as e}from"node:crypto";import{stepCountIs as t,streamText as s}from"ai";import{buildAgentToolset as o}from"../tool-bridge/build-tools.js";import{buildSkillToolset as i}from"../tool-bridge/skill-tool.js";import{ToolRegistry as n}from"../tool-bridge/naming.js";import{buildChatSystemPrompt as r}from"./system-prompt.js";import{readIsError as a}from"../tool-bridge/normalize-result.js";import{ApprovalGate as l}from"../approval/approval-gate.js";import{compactMessages as u}from"./compactor.js";import{AsyncEventQueue as p}from"./event-queue.js";function h(e){return e instanceof Error?e.message:String(e)}function c(e){return"object"==typeof e&&null!==e&&"output"in e}function m(e){return c(e)?h(e.output):h(e)}function d(e){const t=m(e);return t.startsWith("已取消执行")||t.startsWith("策略拒绝执行")?t:void 0}function g(e){return/context[_ -]?length|context window|maximum context|token limit|too many tokens|prompt is too long|input is too long/i.test(h(e))}function v(e){const t=e.inputTokenDetails?.cacheReadTokens,s=e.inputTokenDetails?.cacheWriteTokens,o=e.outputTokenDetails?.reasoningTokens;return{...void 0!==e.inputTokens?{inputTokens:e.inputTokens}:{},...void 0!==e.outputTokens?{outputTokens:e.outputTokens}:{},...void 0!==e.totalTokens?{totalTokens:e.totalTokens}:{},...void 0!==t?{cachedInputTokens:t}:{},...void 0!==s?{cacheWriteTokens:s}:{},...void 0!==o?{reasoningTokens:o}:{}}}function T(e,t){return void 0===e&&void 0===t?void 0:(e??0)+(t??0)}function b(e,t){const s=T(e.cachedInputTokens,t.cachedInputTokens),o=T(e.cacheWriteTokens,t.cacheWriteTokens),i=T(e.reasoningTokens,t.reasoningTokens);return{inputTokens:(e.inputTokens??0)+(t.inputTokens??0),outputTokens:(e.outputTokens??0)+(t.outputTokens??0),totalTokens:(e.totalTokens??0)+(t.totalTokens??0),...void 0!==s?{cachedInputTokens:s}:{},...void 0!==o?{cacheWriteTokens:o}:{},...void 0!==i?{reasoningTokens:i}:{}}}function k(e,t){return void 0===t?e:void 0===e?t:Math.max(e,t)}function y(e,t){const s=e.trim(),o=t.trim();return s.length>0&&o.length>0&&o.startsWith(s)}function f(e){return e.flatMap(e=>{if("assistant"!==e.role||!Array.isArray(e.content))return[e];const t=e.content.filter(e=>"reasoning"!==e.type);return 0===t.length?[]:t.length===e.content.length?[e]:[{...e,content:t}]})}export class AgentSession{id;model;maxSteps;messages;onPersist;onReplace;contextWindow;compaction;turnTimeoutMs;providerOptions;debugEvents;systemPrompt;skillSummaries;gate=new l;tools;registry;emit;activeTurn;sessionUsage={inputTokens:0,outputTokens:0,totalTokens:0};lastInputTokens;needsCompaction=!1;constructor(e){this.id=e.id,this.model=e.model,this.maxSteps=e.maxSteps,this.messages=e.initialMessages?f(e.initialMessages):[],this.onPersist=e.onPersist,this.onReplace=e.onReplace,this.contextWindow=e.contextWindow,this.compaction=e.compaction,this.turnTimeoutMs=e.turnTimeoutMs,this.providerOptions=e.providerOptions,this.debugEvents=e.debugEvents??!1,this.systemPrompt=e.systemPrompt??r(),this.skillSummaries=e.skillLibrary?.list()??[];const t=new n,s=e.skillLibrary?i(e.skillLibrary,t):{},a=o(e.sources,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)},t);this.tools={...s,...a.tools},this.registry=a.registry}async*send(e){if(this.activeTurn)throw new Error("session already has an active turn");const t=new p;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1};this.activeTurn=s,this.runTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"execute",message:h(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async*compact(e="manual"){if(this.activeTurn)throw new Error("session already has an active turn");const t=new p;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1};this.activeTurn=s,this.runCompactionTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"plan",message:h(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async runTurn(o,i,n){const r=Date.now();let l;try{if(this.debug(o,"turn","start",r,{messages:this.messages.length,tools:Object.keys(this.tools).length,maxSteps:this.maxSteps,...void 0!==this.contextWindow?{contextWindow:this.contextWindow}:{},...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}}),this.shouldAutoCompact()){this.debug(o,"compaction","auto requested before turn",r,{messages:this.messages.length,...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}});try{await this.runCompaction(o,"auto",i)}catch(e){return void o.push({type:"error",stage:"plan",message:h(e)})}}if(i.aborted||i.abortController.signal.aborted)return void o.push({type:"error",stage:"execute",message:"aborted"});l=this.messages.length,this.messages.push({role:"user",content:n}),o.push({type:"message-start",messageId:e()}),this.debug(o,"model","calling streamText",r,{messages:this.messages.length,tools:Object.keys(this.tools).length,...void 0!==this.turnTimeoutMs?{timeoutMs:this.turnTimeoutMs}:{}});const u=s({model:this.model,system:this.systemPrompt,messages:this.messages,tools:this.tools,stopWhen:t(this.maxSteps),abortSignal:i.abortController.signal,...this.providerOptions?{providerOptions:this.providerOptions}:{},...void 0!==this.turnTimeoutMs?{timeout:{totalMs:this.turnTimeoutMs}}:{}});this.debug(o,"model","streamText returned",r);let p,c,T,x,w,C,I="",N="",S=!1,M=0,W=!1;const A=this.scheduleDebug(o,"model","waiting for first stream event",r,{messages:this.messages.length});try{for await(const e of u.fullStream){switch(W||(W=!0,this.clearDebugTimer(A),this.debug(o,"model","first stream event",r,{part:e.type})),e.type){case"text-delta":if(!S){const t=N+e.text;if(y(t,n)){N=t;break}if(N.length>0){const t=N+e.text;N="",I+=t,o.push({type:"text-delta",delta:t});break}}I+=e.text,o.push({type:"text-delta",delta:e.text});break;case"tool-call":{S=!0,N="";const t=this.registry.resolve(e.toolName);o.push({type:"tool-call",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,input:e.input});break}case"tool-result":{const t=this.registry.resolve(e.toolName),s=a(e.output);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:e.output,isError:s}),i.aborted||i.abortController.signal.aborted||!s||(C=d(e.output));break}case"tool-error":{const t=this.registry.resolve(e.toolName),s=m(e.error);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:s,isError:!0}),i.aborted||i.abortController.signal.aborted||(C=d(e.error));break}case"finish-step":{const t=v(e.usage);c=k(c,t.inputTokens),M+=1,x=e.finishReason;const s=e.performance.outputTokensPerSecond??e.performance.effectiveOutputTokensPerSecond;void 0!==s&&Number.isFinite(s)&&(T=s),o.push({type:"step-finish",finishReason:e.finishReason,usage:t});break}case"finish":!S&&N.length>0&&(I+=N,o.push({type:"text-delta",delta:N}),N=""),p=v(e.totalUsage);break;case"error":g(e.error)&&(this.needsCompaction=!0),w=h(e.error),o.push({type:"error",stage:"execute",message:w});break;case"abort":i.aborted=!0}if(void 0!==C)break}}finally{this.clearDebugTimer(A)}if(this.debug(o,"model","fullStream finished",r,{textChars:I.length}),void 0!==w)return this.messages.splice(l),void(this.needsCompaction&&await this.recoverFromContextError(o,i));if(i.aborted||i.abortController.signal.aborted)return this.messages.splice(l),void o.push({type:"error",stage:"execute",message:"aborted"});if(void 0!==C){const e=C,t=0===I.length?e:`\n${e}`;o.push({type:"text-delta",delta:t}),this.messages.push({role:"assistant",content:e}),this.debug(o,"persist","persisting messages",r,{appendedMessages:this.messages.length-l}),this.onPersist?.(this.messages.slice(l)),this.debug(o,"persist","messages persisted",r,{totalMessages:this.messages.length}),p&&(this.sessionUsage=b(this.sessionUsage,p));const s=c??p?.inputTokens;return void 0!==s&&(this.lastInputTokens=s),void o.push({type:"message-finish",text:e,...p?{totalUsage:p}:{},sessionUsage:{...this.sessionUsage},...void 0!==c?{contextInputTokens:c}:{},...void 0!==T?{outputTokensPerSecond:T}:{}})}let R;this.debug(o,"model","awaiting response messages",r);const D=this.scheduleDebug(o,"model","still awaiting response messages",r);try{R=(await u.response).messages}catch(e){return this.clearDebugTimer(D),g(e)&&(this.needsCompaction=!0),this.messages.splice(l),o.push({type:"error",stage:"execute",message:h(e)}),void(this.needsCompaction&&await this.recoverFromContextError(o,i))}finally{this.clearDebugTimer(D)}this.debug(o,"model","response messages ready",r,{responseMessages:R.length});const E=f(R);this.messages.push(...E),this.debug(o,"persist","persisting messages",r,{appendedMessages:this.messages.length-l}),this.onPersist?.(this.messages.slice(l)),this.debug(o,"persist","messages persisted",r,{totalMessages:this.messages.length}),p&&(this.sessionUsage=b(this.sessionUsage,p));const P=c??p?.inputTokens;void 0!==P&&(this.lastInputTokens=P);const U=M>=this.maxSteps&&"tool-calls"===x;o.push({type:"message-finish",text:I,...p?{totalUsage:p}:{},sessionUsage:{...this.sessionUsage},...void 0!==c?{contextInputTokens:c}:{},...void 0!==T?{outputTokensPerSecond:T}:{},...U?{stoppedAtStepLimit:!0}:{}})}catch(e){void 0!==l&&this.messages.splice(l),g(e)&&(this.needsCompaction=!0),this.debug(o,"turn","error",r,{message:h(e)}),o.push({type:"error",stage:"execute",message:h(e)}),this.needsCompaction&&await this.recoverFromContextError(o,i)}finally{this.activeTurn===i&&(this.activeTurn=void 0),o.close()}}requestApproval(t){const s=e(),o=this.gate.request(s);return void 0===this.emit?(this.gate.resolve(s,{approved:!1,reason:"approval event could not be delivered"}),o):(this.emit({type:"confirmation-required",approvalId:s,agentName:t.agentName,toolName:t.toolName,input:t.input,...t.reason?{reason:t.reason}:{}}),o)}approve(e){return this.gate.resolve(e,{approved:!0})}reject(e,t){return this.gate.resolve(e,{approved:!1,...t?{reason:t}:{}})}getMessages(){return this.messages}getContextWindow(){return this.contextWindow}getSessionUsage(){return{...this.sessionUsage}}getSkillSummaries(){return this.skillSummaries}setProviderOptions(e){this.providerOptions=e}shouldAutoCompact(){const e=this.compaction;return void 0!==e&&e.enabled&&(this.needsCompaction||void 0!==this.contextWindow&&void 0!==this.lastInputTokens&&this.lastInputTokens/this.contextWindow>=e.threshold)}async runCompactionTurn(e,t,s){try{await this.runCompaction(e,s,t)}catch(t){e.push({type:"error",stage:"plan",message:h(t)})}finally{this.activeTurn===t&&(this.activeTurn=void 0),e.close()}}async runCompaction(e,t,s){const o=Date.now(),i=this.compaction,n=i?.strategy??"summarize";if(this.debug(e,"compaction","start",o,{reason:t,messages:this.messages.length}),e.push({type:"compaction-start",reason:t}),this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});if(!i)return void e.push({type:"context-compacted",reason:t,strategy:n,removed:0,kept:this.messages.length});const r=this.lastInputTokens;let a=i.strategy;const l=s?.abortController.signal;let p;try{p=await u({messages:this.messages,strategy:a,keepRecentTurns:i.keepRecentTurns,keepRecentTokens:i.keepRecentTokens,model:this.model,...l?{abortSignal:l}:{}})}catch(t){if(this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});if("summarize"!==a)throw t;this.debug(e,"compaction","summarize failed, fallback to truncate",o,{message:h(t)}),a="truncate",p=await u({messages:this.messages,strategy:a,keepRecentTurns:i.keepRecentTurns,keepRecentTokens:i.keepRecentTokens,model:this.model,...l?{abortSignal:l}:{}})}if(this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});const c=p.removed>0||p.truncatedTools>0;c&&(this.onReplace?.(p.messages),this.messages.splice(0,this.messages.length,...p.messages),this.lastInputTokens=void 0,this.needsCompaction=!1),e.push({type:"context-compacted",reason:t,strategy:a,removed:p.removed,kept:p.kept,...p.truncatedTools>0?{truncatedTools:p.truncatedTools}:{},...void 0!==r?{beforeInputTokens:r}:{}}),this.debug(e,"compaction","finish",o,{reason:t,strategy:a,removed:p.removed,kept:p.kept,truncatedTools:p.truncatedTools,progressed:c})}async recoverFromContextError(e,t){if(this.compaction?.enabled)try{await this.runCompaction(e,"auto",t)}catch(t){e.push({type:"error",stage:"plan",message:h(t)})}}isTurnAborted(e){return!0===e?.aborted||!0===e?.abortController.signal.aborted}abort(){this.activeTurn&&(this.activeTurn.aborted=!0),this.gate.abortAll(),this.activeTurn?.abortController.abort()}debug(e,t,s,o,i){this.debugEvents&&e.push({type:"debug",stage:t,message:s,...void 0!==o?{elapsedMs:Date.now()-o}:{},...void 0!==i?{data:i}:{}})}scheduleDebug(e,t,s,o,i){if(this.debugEvents)return setTimeout(()=>{this.debug(e,t,s,o,i)},5e3)}clearDebugTimer(e){void 0!==e&&clearTimeout(e)}}
@@ -1 +1 @@
1
- import{generateText as t}from"ai";const e=2e3,n=15e3,o=3.5,r="你在执行上下文 checkpoint 压缩,为另一个将接手这项任务的语言模型撰写交接摘要。请包含:当前进度与已做的关键决定;重要约束、上下文与用户偏好;尚未完成的事项与明确的下一步;继续工作所需的关键数据、示例或引用。简洁、结构化,聚焦于让下一个模型无缝接续,不要寒暄,直接输出摘要正文。";export const SUMMARY_PREFIX="以下摘要由另一个语言模型在压缩早前对话后产出。请据此继续推进、避免重复已完成的工作:";export const SUMMARY_ACK="好的,我已读取之前工作的交接摘要,继续推进。";export function findTurnBoundaries(t){const e=[];return t.forEach((t,n)=>{"user"===t.role&&e.push(n)}),e}function s(t){return Math.ceil(JSON.stringify(t).length/o)}function u(t,e){return t.length<=e?0:t[t.length-e]??0}function a(t,e,n){let o=e[e.length-1]??0,r=0;for(let u=e.length-1;u>=0;u-=1){const a=e[u]??0,l=u+1<e.length?e[u+1]??t.length:t.length;let c=0;for(let e=a;e<l;e+=1){const n=t[e];void 0!==n&&(c+=s(n))}if(u<e.length-1&&r+c>n)return e[u+1]??a;r+=c,o=a}return o}function l(t,e,n){const o=findTurnBoundaries(t);return 0===o.length?0:Math.min(a(t,o,n),u(o,e))}function c(t){if("object"!=typeof t||null===t)return"";const e=t;return"text"===e.type&&"string"==typeof e.text?e.text:"tool-call"===e.type&&"string"==typeof e.toolName?`[调用 ${e.toolName}]`:"tool-result"===e.type?"[工具结果]":""}function i(t){const{content:e}=t;if("string"==typeof e)return`${t.role}: ${e}`;const n=e.map(c).filter(t=>t.length>0).join(" ");return`${t.role}: ${n}`}function g(t){if(t?.aborted)throw new Error("aborted")}async function f(e,o,s){const u=e.map(i).join("\n\n"),{text:a}=await t({model:o,system:r,prompt:u,maxOutputTokens:1024,maxRetries:0,...s?{abortSignal:s}:{},timeout:{totalMs:n}});return[{role:"user",content:`${SUMMARY_PREFIX}\n\n${a}`},{role:"assistant",content:SUMMARY_ACK}]}function m(t){let n=0;return{messages:t.map(t=>{if("tool"!==t.role)return t;const o=t.content.map(t=>{if("tool-result"!==t.type)return t;const o=JSON.stringify(t.output);return o.length<=e?t:(n+=1,{...t,output:{type:"text",value:`[已省略 ${String(o.length)} 字符的工具结果,如需最新内容请重新调用对应工具]`}})});return{...t,content:o}}),truncated:n}}export async function compactMessages(t){g(t.abortSignal);const e=l(t.messages,t.keepRecentTurns,t.keepRecentTokens);if(0===e){const{messages:e,truncated:n}=m(t.messages);return{messages:e,removed:0,kept:e.length,truncatedTools:n}}const n=t.messages.slice(0,e),o=t.messages.slice(e),{messages:r,truncated:s}=m(o),u="summarize"===t.strategy?await f(n,t.model,t.abortSignal):[];return g(t.abortSignal),{messages:[...u,...r],removed:n.length,kept:r.length,truncatedTools:s}}
1
+ import{generateText as t}from"ai";import{readIsError as e}from"../tool-bridge/normalize-result.js";const n=2e3,o=15e3,r=3.5,s="你在执行上下文 checkpoint 压缩,为另一个将接手这项任务的语言模型撰写交接摘要。请包含:当前进度与已做的关键决定;重要约束、上下文与用户偏好;尚未完成的事项与明确的下一步;继续工作所需的关键数据、示例或引用。判断进度时以工具调用记录为准:只把有成功工具结果佐证的操作记为已完成;工具结果失败、被取消或没有对应工具调用的事项,一律列为未完成,即使对话文本中声称已完成。简洁、结构化,聚焦于让下一个模型无缝接续,不要寒暄,直接输出摘要正文。";export const SUMMARY_PREFIX="以下摘要由另一个语言模型在压缩早前对话后产出。请据此继续推进、避免重复已完成的工作:";export const SUMMARY_ACK="好的,我已读取之前工作的交接摘要,继续推进。";export function findTurnBoundaries(t){const e=[];return t.forEach((t,n)=>{"user"===t.role&&e.push(n)}),e}function u(t){return Math.ceil(JSON.stringify(t).length/r)}function i(t,e){return t.length<=e?0:t[t.length-e]??0}function l(t,e,n){let o=e[e.length-1]??0,r=0;for(let s=e.length-1;s>=0;s-=1){const i=e[s]??0,l=s+1<e.length?e[s+1]??t.length:t.length;let a=0;for(let e=i;e<l;e+=1){const n=t[e];void 0!==n&&(a+=u(n))}if(s<e.length-1&&r+a>n)return e[s+1]??i;r+=a,o=i}return o}function a(t,e,n){const o=findTurnBoundaries(t);return 0===o.length?0:Math.min(l(t,o,n),i(o,e))}const c=200;function g(t){const n="object"==typeof t.output&&null!==t.output?t.output:void 0,o="string"==typeof n?.type?n.type:"",r=void 0!==n&&"value"in n?n.value:void 0,s=o.startsWith("error")||e(r),u="object"==typeof r&&null!==r&&"output"in r?r.output:r,i="string"==typeof u?u:JSON.stringify(u)??"",l=i.length>c?`${i.slice(0,c)}…`:i,a=s?"失败":"成功";return l.length>0?`[工具结果·${a}: ${l}]`:`[工具结果·${a}]`}function p(t){if("object"!=typeof t||null===t)return"";const e=t;return"text"===e.type&&"string"==typeof e.text?e.text:"tool-call"===e.type&&"string"==typeof e.toolName?`[调用 ${e.toolName}]`:"tool-result"===e.type?g(e):""}function f(t){const{content:e}=t;if("string"==typeof e)return`${t.role}: ${e}`;const n=e.map(p).filter(t=>t.length>0).join(" ");return`${t.role}: ${n}`}function m(t){if(t?.aborted)throw new Error("aborted")}async function h(e,n,r){const u=e.map(f).join("\n\n"),{text:i}=await t({model:n,system:s,prompt:u,maxOutputTokens:1024,maxRetries:0,...r?{abortSignal:r}:{},timeout:{totalMs:o}});return[{role:"user",content:`${SUMMARY_PREFIX}\n\n${i}`},{role:"assistant",content:SUMMARY_ACK}]}function y(t){let e=0;return{messages:t.map(t=>{if("tool"!==t.role)return t;const o=t.content.map(t=>{if("tool-result"!==t.type)return t;const o=JSON.stringify(t.output);return o.length<=n?t:(e+=1,{...t,output:{type:"text",value:`[已省略 ${String(o.length)} 字符的工具结果,如需最新内容请重新调用对应工具]`}})});return{...t,content:o}}),truncated:e}}export async function compactMessages(t){m(t.abortSignal);const e=a(t.messages,t.keepRecentTurns,t.keepRecentTokens);if(0===e){const{messages:e,truncated:n}=y(t.messages);return{messages:e,removed:0,kept:e.length,truncatedTools:n}}const n=t.messages.slice(0,e),o=t.messages.slice(e),{messages:r,truncated:s}=y(o),u="summarize"===t.strategy?await h(n,t.model,t.abortSignal):[];return m(t.abortSignal),{messages:[...u,...r],removed:n.length,kept:r.length,truncatedTools:s}}
@@ -2,6 +2,7 @@ import type { LanguageModelV4, SharedV4ProviderOptions } from "@ai-sdk/provider"
2
2
  import { McpClientManager } from "@roll-agent/core/mcp/client-manager";
3
3
  import type { RollConfig } from "@roll-agent/core/config/schema";
4
4
  import type { RegisteredAgent } from "@roll-agent/core/types/agent";
5
+ import { type SkillLibrary } from "@roll-agent/core/skills/library";
5
6
  import type { AgentToolSource } from "../tool-bridge/build-tools.ts";
6
7
  import type { ToolPolicy } from "../types/policy.ts";
7
8
  import type { ThreadStore } from "../store/thread-store.ts";
@@ -20,6 +21,8 @@ export interface ConversationEngineOptions {
20
21
  readonly ensureAgentReady?: EnsureAgentReady;
21
22
  readonly debugEvents?: boolean;
22
23
  readonly onAgentBootstrapIssue?: (issue: AgentBootstrapIssue) => void;
24
+ readonly skillLibrary?: SkillLibrary | null;
25
+ readonly onSkillLibraryIssue?: (message: string) => void;
23
26
  }
24
27
  export interface CreateSessionInput {
25
28
  readonly title?: string;
@@ -28,6 +31,11 @@ export interface AgentBootstrapIssue {
28
31
  readonly agentName: string;
29
32
  readonly message: string;
30
33
  }
34
+ export interface EngineContextSummary {
35
+ readonly agentCount: number;
36
+ readonly toolCount: number;
37
+ readonly skillCount: number;
38
+ }
31
39
  export declare class ConversationEngine {
32
40
  private readonly config;
33
41
  private readonly clientManager;
@@ -40,6 +48,8 @@ export declare class ConversationEngine {
40
48
  private readonly explicitAgents;
41
49
  private readonly explicitModel;
42
50
  private readonly explicitSources;
51
+ private readonly explicitSkillLibrary;
52
+ private readonly onSkillLibraryIssue;
43
53
  private readonly onAgentBootstrapIssue;
44
54
  private ready;
45
55
  constructor(options: ConversationEngineOptions);
@@ -48,8 +58,10 @@ export declare class ConversationEngine {
48
58
  private buildSession;
49
59
  private ensureReady;
50
60
  private bootstrap;
61
+ private resolveSkillLibrary;
51
62
  private resolveModel;
52
63
  private resolveProviderName;
53
64
  private resolveModelName;
65
+ getContextSummary(): Promise<EngineContextSummary>;
54
66
  dispose(): Promise<void>;
55
67
  }
@@ -1 +1 @@
1
- import{randomUUID as e}from"node:crypto";import{McpClientManager as t}from"@roll-agent/core/mcp/client-manager";import{createProviderModel as s}from"@roll-agent/core/llm/providers";import{AgentStore as i}from"@roll-agent/core/registry/store";import{resolveTransportWithDevSpawnSpec as o}from"@roll-agent/core/registry/dev-spawn";import{getAgentPid as r,startAgent as n,waitForAgentReady as a}from"@roll-agent/core/registry/process-manager";import{normalizeListedTools as l}from"@roll-agent/core/cli/utils/agent-tools";import{getAgentEnv as c}from"@roll-agent/core/config/helpers";import{AgentSession as d}from"./agent-session.js";import{resolveContextWindow as p}from"./context-window.js";const h=80;function m(e){if("object"!=typeof e||null===e||!("annotations"in e))return;const t=e.annotations;if("object"!=typeof t||null===t)return;const s={};return"readOnlyHint"in t&&"boolean"==typeof t.readOnlyHint&&(s.readOnlyHint=t.readOnlyHint),"destructiveHint"in t&&"boolean"==typeof t.destructiveHint&&(s.destructiveHint=t.destructiveHint),void 0===s.readOnlyHint&&void 0===s.destructiveHint?void 0:s}async function u(e,t,s){"core-managed"===e.runtime.ownership&&(void 0===r(t,e.skill.name)&&n(e,t,s),await a(e))}export class ConversationEngine{config;clientManager;store;policy;maxSteps;providerOptions;ensureAgentReady;debugEvents;explicitAgents;explicitModel;explicitSources;onAgentBootstrapIssue;ready;constructor(e){this.config=e.config,this.clientManager=e.clientManager??new t,this.store=e.store,this.policy=e.policy,this.maxSteps=e.maxSteps??h,this.providerOptions=e.providerOptions,this.ensureAgentReady=e.ensureAgentReady??((e,t)=>u(e,this.config.agents.dataDir,t)),this.debugEvents=e.debugEvents??!1,this.explicitAgents=e.agents,this.explicitModel=e.model,this.explicitSources=e.sources,this.onAgentBootstrapIssue=e.onAgentBootstrapIssue}async createSession(t={}){const s=await this.ensureReady(),i=this.store?this.store.createThread({...t.title?{title:t.title}:{},model:this.resolveModelName()}):e();return this.buildSession(s,i,[])}async resumeSession(e){if(!this.store)throw new Error("resumeSession requires a ThreadStore");if(!this.store.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const t=await this.ensureReady();return this.buildSession(t,e,this.store.getMessages(e))}buildSession(e,t,s){const i=this.store,o=p(this.resolveModelName(),this.config.runtime.contextWindow);return new d({id:t,model:e.model,sources:e.sources,maxSteps:this.maxSteps,compaction:this.config.runtime.compaction,turnTimeoutMs:this.config.runtime.turnTimeoutMs,debugEvents:this.debugEvents,...this.providerOptions?{providerOptions:this.providerOptions}:{},...void 0!==o?{contextWindow:o}:{},...this.policy?{policy:this.policy}:{},initialMessages:s,...i?{onPersist:e=>i.appendMessages(t,e),onReplace:e=>i.replaceMessages(t,e)}:{}})}ensureReady(){return this.ready||(this.ready=this.bootstrap()),this.ready}async bootstrap(){const e=this.explicitModel??this.resolveModel();if(this.explicitSources)return{model:e,sources:this.explicitSources};const t=this.explicitAgents??new i(this.config.agents.dataDir).list(),s=[];for(const i of t)try{const t=o(i),r=c(this.config,i.skill.name);await this.ensureAgentReady(i,r);const n=await this.clientManager.connect(i.skill.name,t,i.installPath,{samplingModel:e,...r?{env:r}:{}}),a=(await n.listTools()).tools,d=l(a).map((e,t)=>({tool:e,annotations:m(a[t])}));s.push({agentName:i.skill.name,client:n,tools:d})}catch(e){this.onAgentBootstrapIssue?.({agentName:i.skill.name,message:e instanceof Error?e.message:String(e)})}return{model:e,sources:s}}resolveModel(){const e=this.resolveProviderName(),t=this.resolveModelName(),i=this.config.llm.providers[e];if(!i)throw new Error(`LLM provider "${e}" 未配置`);return s(e,t,i.apiKey,i.baseUrl)}resolveProviderName(){return this.config.runtime.provider??this.config.llm.defaultProvider}resolveModelName(){return this.config.runtime.model??this.config.llm.defaultModel}async dispose(){await this.clientManager.disconnectAll()}}
1
+ import{randomUUID as e}from"node:crypto";import{McpClientManager as t}from"@roll-agent/core/mcp/client-manager";import{createProviderModel as i}from"@roll-agent/core/llm/providers";import{AgentStore as s}from"@roll-agent/core/registry/store";import{resolveTransportWithDevSpawnSpec as r}from"@roll-agent/core/registry/dev-spawn";import{getAgentPid as o,startAgent as n,waitForAgentReady as l}from"@roll-agent/core/registry/process-manager";import{normalizeListedTools as a}from"@roll-agent/core/cli/utils/agent-tools";import{getAgentEnv as c}from"@roll-agent/core/config/helpers";import{createSkillLibrary as d}from"@roll-agent/core/skills/library";import{AgentSession as p}from"./agent-session.js";import{resolveContextWindow as h}from"./context-window.js";import{buildChatSystemPrompt as u}from"./system-prompt.js";const m=80;function g(e){if("object"!=typeof e||null===e||!("annotations"in e))return;const t=e.annotations;if("object"!=typeof t||null===t)return;const i={};return"readOnlyHint"in t&&"boolean"==typeof t.readOnlyHint&&(i.readOnlyHint=t.readOnlyHint),"destructiveHint"in t&&"boolean"==typeof t.destructiveHint&&(i.destructiveHint=t.destructiveHint),void 0===i.readOnlyHint&&void 0===i.destructiveHint?void 0:i}async function y(e,t,i){"core-managed"===e.runtime.ownership&&(void 0===o(t,e.skill.name)&&n(e,t,i),await l(e))}export class ConversationEngine{config;clientManager;store;policy;maxSteps;providerOptions;ensureAgentReady;debugEvents;explicitAgents;explicitModel;explicitSources;explicitSkillLibrary;onSkillLibraryIssue;onAgentBootstrapIssue;ready;constructor(e){this.config=e.config,this.clientManager=e.clientManager??new t,this.store=e.store,this.policy=e.policy,this.maxSteps=e.maxSteps??m,this.providerOptions=e.providerOptions,this.ensureAgentReady=e.ensureAgentReady??((e,t)=>y(e,this.config.agents.dataDir,t)),this.debugEvents=e.debugEvents??!1,this.explicitAgents=e.agents,this.explicitModel=e.model,this.explicitSources=e.sources,this.explicitSkillLibrary=e.skillLibrary,this.onSkillLibraryIssue=e.onSkillLibraryIssue,this.onAgentBootstrapIssue=e.onAgentBootstrapIssue}async createSession(t={}){const i=await this.ensureReady(),s=this.store?this.store.createThread({...t.title?{title:t.title}:{},model:this.resolveModelName()}):e();return this.buildSession(i,s,[])}async resumeSession(e){if(!this.store)throw new Error("resumeSession requires a ThreadStore");if(!this.store.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const t=await this.ensureReady();return this.buildSession(t,e,this.store.getMessages(e))}buildSession(e,t,i){const s=this.store,r=h(this.resolveModelName(),this.config.runtime.contextWindow),o=e.skillLibrary?.list()??[],n=o.length>0?e.skillLibrary:void 0,l=u({skills:o});return new p({id:t,model:e.model,sources:e.sources,systemPrompt:l,...n?{skillLibrary:n}:{},maxSteps:this.maxSteps,compaction:this.config.runtime.compaction,turnTimeoutMs:this.config.runtime.turnTimeoutMs,debugEvents:this.debugEvents,...this.providerOptions?{providerOptions:this.providerOptions}:{},...void 0!==r?{contextWindow:r}:{},...this.policy?{policy:this.policy}:{},initialMessages:i,...s?{onPersist:e=>s.appendMessages(t,e),onReplace:e=>s.replaceMessages(t,e)}:{}})}ensureReady(){return this.ready||(this.ready=this.bootstrap()),this.ready}async bootstrap(){const e=this.explicitModel??this.resolveModel();if(this.explicitSources)return{model:e,sources:this.explicitSources,...this.explicitSkillLibrary?{skillLibrary:this.explicitSkillLibrary}:{}};const t=this.explicitAgents??new s(this.config.agents.dataDir).list(),i=[];for(const s of t)try{const t=r(s),o=c(this.config,s.skill.name);await this.ensureAgentReady(s,o);const n=await this.clientManager.connect(s.skill.name,t,s.installPath,{samplingModel:e,...o?{env:o}:{}}),l=(await n.listTools()).tools,d=a(l).map((e,t)=>({tool:e,annotations:g(l[t])}));i.push({agentName:s.skill.name,client:n,tools:d})}catch(e){this.onAgentBootstrapIssue?.({agentName:s.skill.name,message:e instanceof Error?e.message:String(e)})}const o=this.resolveSkillLibrary(t);return{model:e,sources:i,...o?{skillLibrary:o}:{}}}resolveSkillLibrary(e){if(null!==this.explicitSkillLibrary)return void 0!==this.explicitSkillLibrary?this.explicitSkillLibrary:d({agents:e,extraDirs:this.config.skills.dirs,...this.onSkillLibraryIssue?{onIssue:this.onSkillLibraryIssue}:{}})}resolveModel(){const e=this.resolveProviderName(),t=this.resolveModelName(),s=this.config.llm.providers[e];if(!s)throw new Error(`LLM provider "${e}" 未配置`);return i(e,t,s.apiKey,s.baseUrl)}resolveProviderName(){return this.config.runtime.provider??this.config.llm.defaultProvider}resolveModelName(){return this.config.runtime.model??this.config.llm.defaultModel}async getContextSummary(){const e=await this.ensureReady();return{agentCount:e.sources.length,toolCount:e.sources.reduce((e,t)=>e+t.tools.length,0),skillCount:e.skillLibrary?.list().length??0}}async dispose(){await this.clientManager.disconnectAll()}}
@@ -0,0 +1,9 @@
1
+ export interface SkillPromptSummary {
2
+ readonly name: string;
3
+ readonly description: string;
4
+ }
5
+ export interface BuildChatSystemPromptOptions {
6
+ readonly skills?: readonly SkillPromptSummary[];
7
+ readonly skillToolId?: string;
8
+ }
9
+ export declare function buildChatSystemPrompt(options?: BuildChatSystemPromptOptions): string;
@@ -0,0 +1 @@
1
+ import{SKILL_TOOL_ID as n}from"../tool-bridge/skill-tool.js";const l=240,e="你是花卷 Roll 的会话助手,运行在 roll chat 里。你通过已注册 Agent 提供的工具(MCP)观察和操作外部世界;你没有独立的文件系统或 shell,工具就是你的全部执行手段。",i=["# 工具使用纪律","- 一切对外部世界的读取和操作都必须通过真实的工具调用完成。绝不虚构工具调用或其结果,也不要用文本描述来代替真正的调用。","- 只有当本会话中出现了对应的成功工具结果,才能说某个操作已完成。没有调用过工具,就如实说明尚未执行。","- 批量任务(例如给多个人回复)必须逐项执行:每一项都真实调用工具、等到结果后再处理下一项;最后按真实结果逐项汇报成功、失败或未执行,不要掩盖失败。","- 工具返回错误时,如实报告错误内容,再决定重试、换方案或向用户求助。不要把失败说成成功,也不要凭空猜测答案。","- 需要确认的工具调用被用户拒绝时,尊重用户的决定,不要换个方式绕过。"].join("\n"),o=["# 任务推进","- 接到任务后持续推进,直到完成或真正被阻塞,不要停在分析或计划阶段。个别工具调用失败不代表任务失败,先尝试自行恢复。","- 除非用户明确只要建议或分析,否则默认用户希望你实际执行。","- 多步任务先用一两句话说明打算怎么做,然后逐步执行,不要把计划本身当成结果。"].join("\n"),t=["# 输出","- 你可以用 thinking/reasoning 做内部推理,但给用户看的最终回复必须写入普通 text 输出通道,不要只写在 reasoning 里。","- 工具调用完成后,在 text 通道给出简洁结论;最终回复不要重复,也不要复述用户输入。","- 像可靠的同事一样汇报:先结论,后必要细节,保持简洁。"].join("\n");function s(n,l){return n.length<=l?n:`${n.slice(0,l-1)}…`}function r(n,e){return["# Skills",`以下是可用的技能说明书(skill)。当任务涉及某个 skill 的领域时,先调用 ${e} 工具(传 name)加载它的完整内容,按其中的流程和约束行事;skill 中的指导优先于你的默认做法。`,n.map(n=>`- ${n.name}: ${s(n.description.replace(/\s+/g," ").trim(),l)}`).join("\n"),`skill 正文提到 references/ 下的文件时,可再次调用 ${e} 并传 reference 参数读取对应文件。`].join("\n")}export function buildChatSystemPrompt(l={}){const s=[e,i,o],c=l.skills??[];return c.length>0&&s.push(r(c,l.skillToolId??n)),s.push(t),s.join("\n\n")}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { ConversationEngine } from "./engine/conversation-engine.ts";
2
2
  export type { AgentBootstrapIssue, ConversationEngineOptions, CreateSessionInput, } from "./engine/conversation-engine.ts";
3
3
  export { AgentSession } from "./engine/agent-session.ts";
4
- export type { AgentSessionOptions, SessionCompactionSettings } from "./engine/agent-session.ts";
4
+ export type { AgentSessionOptions, SessionCompactionSettings, SessionSkillSummary, } from "./engine/agent-session.ts";
5
5
  export { resolveContextWindow, lookupContextWindow } from "./engine/context-window.ts";
6
6
  export { compactMessages, findTurnBoundaries, SUMMARY_PREFIX, SUMMARY_ACK, } from "./engine/compactor.ts";
7
7
  export type { CompactionInput, CompactionResult } from "./engine/compactor.ts";
@@ -27,4 +27,4 @@ export interface BuiltToolset {
27
27
  readonly tools: ToolSet;
28
28
  readonly registry: ToolRegistry;
29
29
  }
30
- export declare function buildAgentToolset(sources: readonly AgentToolSource[], ctx: ToolBridgeContext): BuiltToolset;
30
+ export declare function buildAgentToolset(sources: readonly AgentToolSource[], ctx: ToolBridgeContext, registry?: ToolRegistry): BuiltToolset;
@@ -1 +1 @@
1
- import{jsonSchema as o,tool as t}from"ai";import{preflightToolCall as n}from"@roll-agent/core/tool-runtime/preflight";import{ToolRegistry as r}from"./naming.js";import{normalizeToolResult as e}from"./normalize-result.js";function a(o){return"object"!=typeof o||null===o||Array.isArray(o)?{}:o}function i(o){return`参数校验失败: ${o.map(o=>o.message).join("; ")}`}async function s(o,t,n,r,e){if(!o.policy)return;const a=o.policy.check({agentName:t,toolName:n,input:r,...e?{annotations:e}:{}});if("deny"===a.action)return{output:"策略拒绝执行"+(a.reason?`: ${a.reason}`:""),isError:!0};if("confirm"===a.action){const e=await o.requestApproval({agentName:t,toolName:n,input:r,reason:a.reason});if(!e.approved)return{output:"已取消执行"+(e.reason?`: ${e.reason}`:""),isError:!0}}}export function buildAgentToolset(c,u){const m=new r,l={};for(const r of c){const{client:c,agentName:p}=r;for(const{tool:f,annotations:g}of r.tools){l[m.register(p,f.name)]=t({description:f.description??`${f.name} (via ${p})`,inputSchema:o(f.inputSchema),execute:async(o,t)=>{const r=a(o),m=n(f,r);if(!m.ok)return{output:i(m.issues),isError:!0};const l=await s(u,p,f.name,r,g);if(l)return l;const y=t.abortSignal?{signal:t.abortSignal}:void 0,d=await c.callTool({name:f.name,arguments:r},void 0,y);return e(d)}})}}return{tools:l,registry:m}}
1
+ import{jsonSchema as o,tool as t}from"ai";import{preflightToolCall as n}from"@roll-agent/core/tool-runtime/preflight";import{ToolRegistry as r}from"./naming.js";import{normalizeToolResult as e}from"./normalize-result.js";function a(o){return"object"!=typeof o||null===o||Array.isArray(o)?{}:o}function i(o){return`参数校验失败: ${o.map(o=>o.message).join("; ")}`}async function s(o,t,n,r,e){if(!o.policy)return;const a=o.policy.check({agentName:t,toolName:n,input:r,...e?{annotations:e}:{}});if("deny"===a.action)return{output:"策略拒绝执行"+(a.reason?`: ${a.reason}`:""),isError:!0};if("confirm"===a.action){const e=await o.requestApproval({agentName:t,toolName:n,input:r,reason:a.reason});if(!e.approved)return{output:"已取消执行"+(e.reason?`: ${e.reason}`:""),isError:!0}}}export function buildAgentToolset(c,u,m=new r){const l={};for(const r of c){const{client:c,agentName:p}=r;for(const{tool:f,annotations:g}of r.tools){l[m.register(p,f.name)]=t({description:f.description??`${f.name} (via ${p})`,inputSchema:o(f.inputSchema),execute:async(o,t)=>{const r=a(o),m=n(f,r);if(!m.ok)return{output:i(m.issues),isError:!0};const l=await s(u,p,f.name,r,g);if(l)return l;const y=t.abortSignal?{signal:t.abortSignal}:void 0,d=await c.callTool({name:f.name,arguments:r},void 0,y);return e(d)}})}}return{tools:l,registry:m}}
@@ -0,0 +1,21 @@
1
+ import { type ToolSet } from "ai";
2
+ import { z } from "zod";
3
+ import { SKILL_TOOL_ID, type SkillLibrary } from "@roll-agent/core/skills/library";
4
+ import type { ToolRegistry } from "./naming.ts";
5
+ import type { NormalizedToolResult } from "./normalize-result.ts";
6
+ export declare const SKILL_TOOL_AGENT_NAME = "roll";
7
+ export declare const SKILL_TOOL_NAME = "skill";
8
+ export { SKILL_TOOL_ID };
9
+ declare const skillToolInputSchema: z.ZodObject<{
10
+ name: z.ZodString;
11
+ reference: z.ZodOptional<z.ZodString>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ name: string;
14
+ reference?: string | undefined;
15
+ }, {
16
+ name: string;
17
+ reference?: string | undefined;
18
+ }>;
19
+ export type SkillToolInput = z.infer<typeof skillToolInputSchema>;
20
+ export declare function executeSkillTool(library: SkillLibrary, input: SkillToolInput): NormalizedToolResult;
21
+ export declare function buildSkillToolset(library: SkillLibrary, registry: ToolRegistry): ToolSet;
@@ -0,0 +1 @@
1
+ import{tool as e}from"ai";import{z as r}from"zod";import{SKILL_TOOL_ID as n}from"@roll-agent/core/skills/library";export const SKILL_TOOL_AGENT_NAME="roll";export const SKILL_TOOL_NAME="skill";export{n as SKILL_TOOL_ID};const t=6e4,o=r.object({name:r.string().min(1).describe("要加载的 skill 名称(见 system prompt 中的 Skills 目录)"),reference:r.string().min(1).optional().describe("可选:加载该 skill 的 references/ 下的某个文件,传相对路径如 references/workflows.md")});function l(e){return e.length<=t?e:`${e.slice(0,t)}\n\n[内容过长已截断,共 ${String(e.length)} 字符]`}function i(e,r){const n=e.load(r);if(!n){return{output:`skill "${r}" 不存在。可用 skill: ${e.list().map(e=>e.name).join(", ")}`,isError:!0}}const t=n.referencePaths.length>0?`\n\n可用 references(传 reference 参数加载):\n${n.referencePaths.map(e=>`- ${e}`).join("\n")}`:"";return{output:`${l(n.content)}${t}`,isError:!1}}function s(e,r,n){const t=e.loadReference(r,n);return void 0===t?{output:`skill "${r}" 中不存在 reference "${n}"(仅支持 skill 目录内 references/ 下的文件)`,isError:!0}:{output:l(t),isError:!1}}export function executeSkillTool(e,r){return void 0!==r.reference?s(e,r.name,r.reference):i(e,r.name)}export function buildSkillToolset(r,n){const t=n.register("roll","skill");return{[t]:e({description:"加载一个 skill(技能说明书)的完整内容,或其 references/ 下的文件。执行涉及某个 skill 领域的任务前,先用它读取流程与约束。",inputSchema:o,execute:e=>executeSkillTool(r,e)})}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/runtime",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^22.0.0",
36
- "@roll-agent/core": "0.13.0"
36
+ "@roll-agent/core": "0.15.0"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/obfuscate.mjs",