@roll-agent/runtime 0.11.0 → 0.13.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.
Files changed (31) hide show
  1. package/dist/engine/agent-session.d.ts +1 -0
  2. package/dist/engine/agent-session.js +1 -1
  3. package/dist/engine/cancelled-turn-recovery.js +1 -1
  4. package/dist/engine/compaction-checkpoint.d.ts +72 -4
  5. package/dist/engine/compaction-checkpoint.js +1 -1
  6. package/dist/engine/compaction-semantic-state.d.ts +3 -0
  7. package/dist/engine/compaction-semantic-state.js +1 -1
  8. package/dist/engine/system-prompt.js +1 -1
  9. package/dist/index.d.ts +4 -2
  10. package/dist/index.js +1 -1
  11. package/dist/server/runtime-client-request-coordinator.d.ts +70 -0
  12. package/dist/server/runtime-client-request-coordinator.js +1 -0
  13. package/dist/server/runtime-protocol-adapter.d.ts +23 -3
  14. package/dist/server/runtime-protocol-adapter.js +1 -1
  15. package/dist/server/runtime-server.d.ts +15 -1
  16. package/dist/server/runtime-server.js +1 -1
  17. package/dist/service/runtime-service.d.ts +22 -1
  18. package/dist/service/runtime-service.js +1 -1
  19. package/dist/tool-bridge/bash-tool.d.ts +3 -0
  20. package/dist/tool-bridge/bash-tool.js +1 -1
  21. package/dist/tool-bridge/build-tools.d.ts +8 -1
  22. package/dist/tool-bridge/build-tools.js +1 -1
  23. package/dist/tool-bridge/normalize-result.d.ts +17 -1
  24. package/dist/tool-bridge/normalize-result.js +1 -1
  25. package/dist/tool-bridge/session-exec-tool.d.ts +3 -0
  26. package/dist/tool-bridge/session-exec-tool.js +1 -1
  27. package/dist/tool-bridge/shell-explanation.d.ts +2 -0
  28. package/dist/tool-bridge/shell-explanation.js +1 -0
  29. package/dist/tool-bridge/tool-execution-record.js +1 -1
  30. package/dist/types/events.d.ts +2 -0
  31. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import { RUNTIME_METHODS, type InitializeParams, type InitializeResult, type RuntimeEventEnvelope, type RuntimeInstanceId, type RuntimeMethodParams, type RuntimeMethodResult, type RuntimeProtocolErrorData } from "@roll-agent/protocol";
1
+ import { RUNTIME_METHODS, approvalIdSchema, type ApprovalResolution, type InitializeParams, type InitializeResult, type RuntimeEventEnvelope, type RuntimeInstanceId, type RuntimeMethodParams, type RuntimeMethodResult, type RuntimeProtocolErrorData, type ThreadId, type TurnId } from "@roll-agent/protocol";
2
2
  import type { AgentSession } from "../engine/agent-session.ts";
3
3
  import { ThreadStore } from "../store/thread-store.ts";
4
4
  type RollErrorCode = RuntimeProtocolErrorData["rollCode"];
@@ -21,6 +21,17 @@ export interface RuntimeServiceOptions {
21
21
  */
22
22
  readonly reasoningSummaryProjector?: (delta: string) => string | undefined;
23
23
  }
24
+ export interface RuntimeApprovalIdentity {
25
+ readonly threadId: ThreadId;
26
+ readonly turnId: TurnId;
27
+ readonly approvalId: ReturnType<typeof approvalIdSchema.parse>;
28
+ }
29
+ export type RuntimeApprovalDecision = {
30
+ readonly decision: "approve";
31
+ } | {
32
+ readonly decision: "reject";
33
+ readonly reason?: string;
34
+ };
24
35
  export declare class RuntimeServiceError extends Error {
25
36
  readonly rollCode: RollErrorCode;
26
37
  readonly retryable: boolean;
@@ -74,6 +85,11 @@ export declare class RuntimeService {
74
85
  startTurn(params: RuntimeMethodParams<"turn.start">): Promise<RuntimeMethodResult<"turn.start">>;
75
86
  cancelTurn(params: RuntimeMethodParams<"turn.cancel">): Promise<RuntimeMethodResult<"turn.cancel">>;
76
87
  respondApproval(params: RuntimeMethodParams<"approval.respond">): Promise<RuntimeMethodResult<"approval.respond">>;
88
+ resolvePendingApproval(identity: RuntimeApprovalIdentity, decision: RuntimeApprovalDecision): RuntimeMethodResult<"approval.respond">;
89
+ private cancelPendingApproval;
90
+ failPendingApprovalInteraction(identity: RuntimeApprovalIdentity, reason: string, resolution?: Extract<ApprovalResolution, {
91
+ readonly status: "cancelled" | "expired";
92
+ }>): Promise<boolean>;
77
93
  getOperation(params: RuntimeMethodParams<"operation.get">): RuntimeMethodResult<"operation.get">;
78
94
  close(): Promise<void>;
79
95
  private assertOpen;
@@ -85,5 +101,10 @@ export declare class RuntimeService {
85
101
  private settleTurnOwner;
86
102
  private projectSessionEvent;
87
103
  private projectToolOutcome;
104
+ private findPendingApproval;
105
+ private requirePendingApproval;
106
+ private emitApprovalResolved;
107
+ private cancelPendingApprovalsForTurn;
108
+ private emitTurnTerminal;
88
109
  }
89
110
  export {};
@@ -1 +1 @@
1
- import{createHash as e,randomUUID as t}from"node:crypto";import{RUNTIME_ERROR_CODES as r,RUNTIME_FEATURES as s,RUNTIME_METHODS as n,RUNTIME_PROTOCOL_VERSION as i,approvalIdSchema as a,operationIdSchema as o,parseRuntimeMethodResult as d,runtimeInstanceIdSchema as u,streamIdSchema as h,threadIdSchema as l}from"@roll-agent/protocol";import{createSafeCapabilitySnapshot as c}from"../engine/capability-manifest.js";import{isSensitiveFieldName as m,redactSecretText as p,toRedactedToolExecutionRecordSummary as I}from"../tool-bridge/tool-execution-record.js";import{ThreadStore as v}from"../store/thread-store.js";const g=4194304,f=500,y=1e4,T=16e3,w=64,S=128,q=16,O=new Set(["_meta","authorization","cookie","password","providerOptions","raw","secret","token"]);export class RuntimeServiceError extends Error{rollCode;retryable;details;constructor(e,t,r={}){super(t),this.name="RuntimeServiceError",this.rollCode=e,this.retryable=r.retryable??!1,this.details=r.details}}function x(t){const s=JSON.stringify(t);if(void 0===s)throw new RuntimeServiceError(r.invalidParams,"无法为 mutation 请求生成稳定指纹");return e("sha256").update(s).digest("hex")}export class MutationRequestCache{maxEntries;inFlight=new Map;settled=new Map;constructor(e){if(!Number.isInteger(e)||e<1)throw new Error("idempotencyCacheEntries must be a positive integer");this.maxEntries=e}run(e,t,r,s){const n=x(r),i=this.inFlight.get(e);if(void 0!==i)return this.assertMatchingRequest(e,t,n,i),i.response.then(e=>d(t,e));const a=this.settled.get(e);if(void 0!==a)return this.assertMatchingRequest(e,t,n,a),this.settled.delete(e),this.settled.set(e,a),a.response.then(e=>d(t,e));const o=Promise.resolve().then(s),u={method:t,fingerprint:n,response:o};return this.inFlight.set(e,u),o.then(()=>{this.settle(e,u)},()=>{this.settle(e,u)}),o}clear(){this.inFlight.clear(),this.settled.clear()}assertMatchingRequest(e,t,s,n){if(n.method!==t||n.fingerprint!==s)throw new RuntimeServiceError(r.invalidParams,`requestId "${e}" 已用于不同请求`)}settle(e,t){if(this.inFlight.get(e)===t)for(this.inFlight.delete(e),this.settled.set(e,t);this.settled.size>this.maxEntries;){const e=this.settled.keys().next().value;if(void 0===e)return;this.settled.delete(e)}}}function E(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function R(e,t,r=0){if(void 0!==t&&(O.has(t.toLowerCase())||m(t)))return"[redacted]";if(r>=q)return"[nested value omitted]";if(null===e||"boolean"==typeof e)return e;if("string"==typeof e){const t=p(e);return t.length<=T?t:`${t.slice(0,T)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,w).map(e=>R(e,void 0,r+1));return e.length>w&&t.push(`[${String(e.length-w)} items omitted]`),t}if(E(e)){const t={};for(const[s,n]of Object.entries(e).slice(0,S))void 0!==n&&(t[s]=R(n,s,r+1));return Object.keys(e).length>S&&(t._omitted=`${String(Object.keys(e).length-S)} keys omitted`),t}return void 0===e?null:String(e)}function A(e){return e instanceof Error?e.message:String(e)}function N(e){const t=e.content;return"string"==typeof t?t:Array.isArray(t)?t.flatMap(e=>E(e)&&"text"===e.type&&"string"==typeof e.text?[e.text]:[]).join(""):""}function C(e){return l.parse(e)}function b(e,t){return{id:C(t.id),...void 0!==t.title?{title:t.title}:{},...void 0!==t.model?{model:t.model}:{},createdAt:t.createdAt,updatedAt:t.updatedAt,messageCount:e.countTranscriptMessages(t.id)}}function j(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=p(N(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:t.length>0?[{type:"text",text:t}]:[]}}function M(e){const t=I(e);return{id:o.parse(t.id),sequence:e.sequence,toolCallId:t.toolCallId,agentName:t.agentName,toolName:t.toolName,createdAt:t.createdAt,outcome:t.outcome,display:R(t.display,void 0)}}function F(e,t){return{id:a.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:R(t.input,void 0),...void 0!==t.reason?{reason:p(t.reason)}:{}}}export class RuntimeService{runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;sessions=new Map;activeTurns=new Map;activeTurnOwners=new Map;settledTurnOwners=new Map;pendingApprovals=new Map;listeners=new Set;mutationRequests;sequence=0;closing=!1;constructor(e,r,s={}){this.engine=e,this.store=r,this.serverName=s.serverName??"roll-runtime",this.serverVersion=s.serverVersion??"1.0",this.runtimeVersion=s.runtimeVersion??"development",this.maxFrameBytes=s.maxFrameBytes??g,this.idempotencyCacheEntries=s.idempotencyCacheEntries??y,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,this.runtimeInstanceId=u.parse(t())}initialize(e){if(!e.protocolVersions.includes(i))throw new RuntimeServiceError(r.protocolVersionUnsupported,`不支持客户端协议版本:${e.protocolVersions.join(", ")}`,{details:{supportedVersions:[i]}});return{protocolVersion:i,runtimeInstanceId:this.runtimeInstanceId,server:{name:this.serverName,version:this.serverVersion,runtimeVersion:this.runtimeVersion},features:s.filter(e=>"reasoning-summary"!==e||void 0!==this.reasoningSummaryProjector),limits:{maxFrameBytes:this.maxFrameBytes,maxPageSize:f,eventReplay:!1,idempotencyCacheEntries:this.idempotencyCacheEntries}}}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}listThreads(e){this.assertOpen();const t=void 0===e.cursor?0:Number(e.cursor);if(!Number.isSafeInteger(t)||t<0)throw new RuntimeServiceError(r.invalidParams,"thread.list cursor 超出安全整数范围");const s=this.store.listThreads(),n=s.slice(t,t+e.limit).map(e=>b(this.store,e)),i=t+n.length;return{items:n,nextCursor:i<s.length?String(i):null}}async createThread(e){return this.mutationRequests.run(e.requestId,n.threadCreate,e,async()=>{this.assertOpen();const t=await this.engine.createSession(void 0!==e.title?{title:e.title}:{});return this.sessions.set(t.id,t),{thread:b(this.store,this.requireThread(t.id))}})}async openThread(e){return await this.requireSession(e.threadId),this.snapshotThread({threadId:e.threadId,limit:100})}snapshotThread(e){this.assertOpen();const t=this.requireThread(e.threadId),r=this.store.listRecentTranscriptMessages(e.threadId,{...void 0!==e.messageBeforeSequence?{beforeSequence:e.messageBeforeSequence}:{},limit:e.limit}),s=this.store.listRecentToolExecutions(e.threadId,{...void 0!==e.operationBeforeSequence?{beforeSequence:e.operationBeforeSequence}:{},limit:e.limit}),n=this.activeTurns.get(e.threadId),i=void 0===n?void 0:{id:n.turnId,status:n.status,startedAt:n.startedAt};return{thread:b(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=j(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map(M),nextBeforeSequence:s.nextBeforeSequence??null},...void 0!==i?{activeTurn:i}:{},pendingApprovals:[...this.pendingApprovals.values()].filter(t=>t.threadId===e.threadId).map(e=>e.approval),transcriptCompleteness:this.store.getTranscriptCompleteness(e.threadId)}}async renameThread(e){return this.mutationRequests.run(e.requestId,n.threadRename,e,()=>(this.assertOpen(),this.requireThread(e.threadId),this.store.updateTitle(e.threadId,e.title),{thread:b(this.store,this.requireThread(e.threadId))}))}async deleteThread(e){return this.mutationRequests.run(e.requestId,n.threadDelete,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))throw new RuntimeServiceError(r.threadBusy,`Thread "${e.threadId}" 仍有活动 Turn`,{retryable:!0});const t=this.sessions.get(e.threadId);return void 0!==t&&(await t.close(),this.sessions.delete(e.threadId)),this.store.deleteThread(e.threadId),{deleted:!0}})}async detachThread(e){return this.mutationRequests.run(e.requestId,n.threadDetach,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))return{detached:!1};const t=this.sessions.get(e.threadId);return void 0===t?{detached:!1}:(await t.close(),this.sessions.delete(e.threadId),{detached:!0})})}async threadCapabilities(e){const t=await this.requireSession(e.threadId);return{manifest:R(c(t.getCapabilityManifest(),t.getCapabilityTurnContext()),void 0)}}async startTurn(e){return this.mutationRequests.run(e.requestId,n.turnStart,e,async()=>{this.assertOpen();const t=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==t){if(t===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(r.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(r.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const s=await this.requireSession(e.threadId),n=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==n){if(n===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(r.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(r.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const i={threadId:e.threadId,turnId:e.turnId,session:s,startedAt:(new Date).toISOString(),status:"running"};return this.activeTurns.set(e.threadId,i),this.activeTurnOwners.set(e.turnId,e.threadId),this.emit(e.threadId,e.turnId,{type:"turn.started"}),this.driveTurn(i,e.input.text).catch(()=>{}),{accepted:!0,turnId:e.turnId}})}async cancelTurn(e){return this.mutationRequests.run(e.requestId,n.turnCancel,e,()=>{this.assertOpen();const t=this.activeTurns.get(e.threadId);if(void 0===t||t.turnId!==e.turnId)throw new RuntimeServiceError(r.turnNotFound,`Turn "${e.turnId}" 不存在或已结束`);return t.status="cancelling",{cancelling:t.session.cancel()}})}async respondApproval(e){return this.mutationRequests.run(e.requestId,n.approvalRespond,e,()=>{this.assertOpen();const t=this.pendingApprovals.get(e.approvalId);if(void 0===t||t.threadId!==e.threadId||t.approval.turnId!==e.turnId)throw new RuntimeServiceError(r.approvalNotFound,`Approval "${e.approvalId}" 不存在或已失效`);if(!("approve"===e.decision?t.session.approve(e.approvalId):t.session.reject(e.approvalId,e.reason)))throw new RuntimeServiceError(r.approvalNotFound,`Approval "${e.approvalId}" 已失效`);return this.pendingApprovals.delete(e.approvalId),{resolved:!0}})}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:M(t)}}async close(){if(this.closing)return;this.closing=!0;for(const e of this.activeTurns.values())e.session.cancel();const e=[...this.sessions.values()];this.sessions.clear(),this.activeTurns.clear(),this.activeTurnOwners.clear(),this.settledTurnOwners.clear(),this.pendingApprovals.clear(),await Promise.allSettled(e.map(e=>e.close())),this.mutationRequests.clear()}assertOpen(){if(this.closing)throw new RuntimeServiceError(r.runtimeClosing,"Runtime 正在关闭",{retryable:!0})}requireThread(e){const t=this.store.getThread(e);if(void 0===t)throw new RuntimeServiceError(r.threadNotFound,`Thread "${e}" 不存在`);return t}async requireSession(e){this.assertOpen(),this.requireThread(e);const t=this.sessions.get(e);if(void 0!==t)return t;const r=await this.engine.resumeSession(e);return this.sessions.set(e,r),r}emit(e,t,r){const s={protocolVersion:i,runtimeInstanceId:this.runtimeInstanceId,sequence:this.sequence,timestamp:(new Date).toISOString(),threadId:e,...void 0!==t?{turnId:t}:{},event:r};this.sequence+=1;for(const e of this.listeners)e(s)}async driveTurn(e,t){const r={streamId:void 0,text:"",terminalEmitted:!1};try{for await(const s of e.session.send(t))this.projectSessionEvent(e,r,s);r.terminalEmitted||this.emit(e.threadId,e.turnId,{type:"turn.completed"})}catch(t){r.terminalEmitted||this.emit(e.threadId,e.turnId,{type:"turn.failed",stage:"execute",message:p(A(t))})}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e);for(const[t,r]of this.pendingApprovals)r.approval.turnId===e.turnId&&this.pendingApprovals.delete(t)}}findTurnOwner(e,t){const r=this.activeTurnOwners.get(e);if(void 0!==r)return r;const s=this.settledTurnOwners.get(e);return s===t&&(this.settledTurnOwners.delete(e),this.settledTurnOwners.set(e,s)),s}settleTurnOwner(e){if(this.activeTurnOwners.get(e.turnId)===e.threadId&&(this.activeTurnOwners.delete(e.turnId),!this.closing))for(this.settledTurnOwners.set(e.turnId,e.threadId);this.settledTurnOwners.size>this.idempotencyCacheEntries;){const e=this.settledTurnOwners.keys().next().value;if(void 0===e)return;this.settledTurnOwners.delete(e)}}projectSessionEvent(e,r,s){switch(s.type){case"message-start":{const s=h.parse(t());return r.streamId=s,r.text="",void this.emit(e.threadId,e.turnId,{type:"message.started",streamId:s})}case"text-delta":{const n=r.streamId??h.parse(t());return void 0===r.streamId&&(r.streamId=n,this.emit(e.threadId,e.turnId,{type:"message.started",streamId:n})),r.text+=s.delta,void this.emit(e.threadId,e.turnId,{type:"message.delta",streamId:n,delta:s.delta})}case"message-finish":{const n=r.streamId??h.parse(t());return this.emit(e.threadId,e.turnId,{type:"message.completed",streamId:n,text:p(s.text||r.text)}),this.emit(e.threadId,e.turnId,{type:"turn.completed"}),void(r.terminalEmitted=!0)}case"reasoning-delta":{const t=this.reasoningSummaryProjector?.(s.delta);return void(void 0!==t&&t.length>0&&this.emit(e.threadId,e.turnId,{type:"reasoning.summary.delta",reasoningId:s.reasoningId,delta:p(t)}))}case"tool-call":return void this.emit(e.threadId,e.turnId,{type:"tool.started",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,input:R(s.input,void 0)});case"tool-output-delta":return void this.emit(e.threadId,e.turnId,{type:"tool.output",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,stream:s.stream,delta:p(s.delta)});case"tool-result":return void this.emit(e.threadId,e.turnId,{type:"tool.completed",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,...void 0!==s.executionId?{operationId:o.parse(s.executionId)}:{},...void 0!==s.outcome?{outcome:this.projectToolOutcome(s.outcome)}:{},display:R(s.display,void 0)});case"confirmation-required":{const t=F(e.turnId,s);return this.pendingApprovals.set(t.id,{threadId:e.threadId,session:e.session,approval:t}),void this.emit(e.threadId,e.turnId,{type:"approval.required",approval:t})}case"turn-cancelled":this.emit(e.threadId,e.turnId,{type:"turn.cancelled",reason:s.reason,message:p(s.message)}),r.terminalEmitted=!0;break;case"error":this.emit(e.threadId,e.turnId,{type:"turn.failed",stage:s.stage,message:p(s.message)}),r.terminalEmitted=!0}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:p(e.reason)}:{}}}}
1
+ import{createHash as e,randomUUID as t}from"node:crypto";import{APPROVAL_EXPLANATION_PREVIEW_KEY as r,RUNTIME_ERROR_CODES as s,RUNTIME_FEATURES as n,RUNTIME_METHODS as i,RUNTIME_PROTOCOL_VERSION as a,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as o,approvalExplanationSchema as d,approvalIdSchema as u,operationIdSchema as l,parseRuntimeMethodResult as c,runtimeInstanceIdSchema as h,runtimeProtocolVersionSchema as p,streamIdSchema as m,threadIdSchema as v}from"@roll-agent/protocol";import{createSafeCapabilitySnapshot as I}from"../engine/capability-manifest.js";import{isSensitiveFieldName as g,redactSecretText as f,toRedactedToolExecutionRecordSummary as y}from"../tool-bridge/tool-execution-record.js";import{ThreadStore as T}from"../store/thread-store.js";const w=4194304,q=500,A=1e4,S=16e3,x=64,O=128,R=16,E=new Set(["_meta","authorization","cookie","password","providerOptions","raw","secret","token"]);export class RuntimeServiceError extends Error{rollCode;retryable;details;constructor(e,t,r={}){super(t),this.name="RuntimeServiceError",this.rollCode=e,this.retryable=r.retryable??!1,this.details=r.details}}function N(t){const r=JSON.stringify(t);if(void 0===r)throw new RuntimeServiceError(s.invalidParams,"无法为 mutation 请求生成稳定指纹");return e("sha256").update(r).digest("hex")}export class MutationRequestCache{maxEntries;inFlight=new Map;settled=new Map;constructor(e){if(!Number.isInteger(e)||e<1)throw new Error("idempotencyCacheEntries must be a positive integer");this.maxEntries=e}run(e,t,r,s){const n=N(r),i=this.inFlight.get(e);if(void 0!==i)return this.assertMatchingRequest(e,t,n,i),i.response.then(e=>c(t,e));const a=this.settled.get(e);if(void 0!==a)return this.assertMatchingRequest(e,t,n,a),this.settled.delete(e),this.settled.set(e,a),a.response.then(e=>c(t,e));const o=Promise.resolve().then(s),d={method:t,fingerprint:n,response:o};return this.inFlight.set(e,d),o.then(()=>{this.settle(e,d)},()=>{this.settle(e,d)}),o}clear(){this.inFlight.clear(),this.settled.clear()}assertMatchingRequest(e,t,r,n){if(n.method!==t||n.fingerprint!==r)throw new RuntimeServiceError(s.invalidParams,`requestId "${e}" 已用于不同请求`)}settle(e,t){if(this.inFlight.get(e)===t)for(this.inFlight.delete(e),this.settled.set(e,t);this.settled.size>this.maxEntries;){const e=this.settled.keys().next().value;if(void 0===e)return;this.settled.delete(e)}}}function C(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function b(e,t,r=0){if(void 0!==t&&(E.has(t.toLowerCase())||g(t)))return"[redacted]";if(r>=R)return"[nested value omitted]";if(null===e||"boolean"==typeof e)return e;if("string"==typeof e){const t=f(e);return t.length<=S?t:`${t.slice(0,S)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,x).map(e=>b(e,void 0,r+1));return e.length>x&&t.push(`[${String(e.length-x)} items omitted]`),t}if(C(e)){const t={};for(const[s,n]of Object.entries(e).slice(0,O))void 0!==n&&(t[s]=b(n,s,r+1));return Object.keys(e).length>O&&(t._omitted=`${String(Object.keys(e).length-O)} keys omitted`),t}return void 0===e?null:String(e)}function P(e){return e instanceof Error?e.message:String(e)}function F(e){const t=e.content;return"string"==typeof t?t:Array.isArray(t)?t.flatMap(e=>C(e)&&"text"===e.type&&"string"==typeof e.text?[e.text]:[]).join(""):""}function j(e){return v.parse(e)}function M(e,t){return{id:j(t.id),...void 0!==t.title?{title:t.title}:{},...void 0!==t.model?{model:t.model}:{},createdAt:t.createdAt,updatedAt:t.updatedAt,messageCount:e.countTranscriptMessages(t.id)}}function V(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=f(F(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:t.length>0?[{type:"text",text:t}]:[]}}function $(e){const t=y(e);return{id:l.parse(t.id),sequence:e.sequence,toolCallId:t.toolCallId,agentName:t.agentName,toolName:t.toolName,createdAt:t.createdAt,outcome:{kind:t.outcome.kind,..."reason"in t.outcome&&void 0!==t.outcome.reason?{reason:t.outcome.reason}:{}},display:b(t.display,void 0)}}function B(e,t){const s=b(t.input,void 0),n=void 0===t.explanation?void 0:d.safeParse(f(t.explanation)),i=!0===n?.success&&C(s)?{...s,[r]:n.data}:s;return{id:u.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:i,...void 0!==t.reason?{reason:f(t.reason)}:{}}}export class RuntimeService{runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;sessions=new Map;activeTurns=new Map;activeTurnOwners=new Map;settledTurnOwners=new Map;pendingApprovals=new Map;listeners=new Set;mutationRequests;sequence=0;closing=!1;constructor(e,r,s={}){this.engine=e,this.store=r,this.serverName=s.serverName??"roll-runtime",this.serverVersion=s.serverVersion??"1.0",this.runtimeVersion=s.runtimeVersion??"development",this.maxFrameBytes=s.maxFrameBytes??w,this.idempotencyCacheEntries=s.idempotencyCacheEntries??A,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,this.runtimeInstanceId=h.parse(t())}initialize(e){let t;this.assertOpen();for(const r of e.protocolVersions){const e=p.safeParse(r);if(e.success){t=e.data;break}}if(void 0===t)throw new RuntimeServiceError(s.protocolVersionUnsupported,`不支持客户端协议版本:${e.protocolVersions.join(", ")}`,{details:{supportedVersions:o}});return{protocolVersion:t,runtimeInstanceId:this.runtimeInstanceId,server:{name:this.serverName,version:this.serverVersion,runtimeVersion:this.runtimeVersion},features:n.filter(e=>"reasoning-summary"!==e||void 0!==this.reasoningSummaryProjector),limits:{maxFrameBytes:this.maxFrameBytes,maxPageSize:q,eventReplay:!1,idempotencyCacheEntries:this.idempotencyCacheEntries}}}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}listThreads(e){this.assertOpen();const t=void 0===e.cursor?0:Number(e.cursor);if(!Number.isSafeInteger(t)||t<0)throw new RuntimeServiceError(s.invalidParams,"thread.list cursor 超出安全整数范围");const r=this.store.listThreads(),n=r.slice(t,t+e.limit).map(e=>M(this.store,e)),i=t+n.length;return{items:n,nextCursor:i<r.length?String(i):null}}async createThread(e){return this.mutationRequests.run(e.requestId,i.threadCreate,e,async()=>{this.assertOpen();const t=await this.engine.createSession(void 0!==e.title?{title:e.title}:{});return this.sessions.set(t.id,t),{thread:M(this.store,this.requireThread(t.id))}})}async openThread(e){return await this.requireSession(e.threadId),this.snapshotThread({threadId:e.threadId,limit:100})}snapshotThread(e){this.assertOpen();const t=this.requireThread(e.threadId),r=this.store.listRecentTranscriptMessages(e.threadId,{...void 0!==e.messageBeforeSequence?{beforeSequence:e.messageBeforeSequence}:{},limit:e.limit}),s=this.store.listRecentToolExecutions(e.threadId,{...void 0!==e.operationBeforeSequence?{beforeSequence:e.operationBeforeSequence}:{},limit:e.limit}),n=this.activeTurns.get(e.threadId),i=void 0===n?void 0:{id:n.turnId,status:n.status,startedAt:n.startedAt};return{thread:M(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=V(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map($),nextBeforeSequence:s.nextBeforeSequence??null},...void 0!==i?{activeTurn:i}:{},pendingApprovals:[...this.pendingApprovals.values()].filter(t=>t.threadId===e.threadId).map(e=>e.approval),transcriptCompleteness:this.store.getTranscriptCompleteness(e.threadId)}}async renameThread(e){return this.mutationRequests.run(e.requestId,i.threadRename,e,()=>(this.assertOpen(),this.requireThread(e.threadId),this.store.updateTitle(e.threadId,e.title),{thread:M(this.store,this.requireThread(e.threadId))}))}async deleteThread(e){return this.mutationRequests.run(e.requestId,i.threadDelete,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))throw new RuntimeServiceError(s.threadBusy,`Thread "${e.threadId}" 仍有活动 Turn`,{retryable:!0});const t=this.sessions.get(e.threadId);return void 0!==t&&(await t.close(),this.sessions.delete(e.threadId)),this.store.deleteThread(e.threadId),{deleted:!0}})}async detachThread(e){return this.mutationRequests.run(e.requestId,i.threadDetach,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))return{detached:!1};const t=this.sessions.get(e.threadId);return void 0===t?{detached:!1}:(await t.close(),this.sessions.delete(e.threadId),{detached:!0})})}async threadCapabilities(e){const t=await this.requireSession(e.threadId);return{manifest:b(I(t.getCapabilityManifest(),t.getCapabilityTurnContext()),void 0)}}async startTurn(e){return this.mutationRequests.run(e.requestId,i.turnStart,e,async()=>{this.assertOpen();const t=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==t){if(t===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(s.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(s.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const r=await this.requireSession(e.threadId),n=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==n){if(n===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(s.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId))throw new RuntimeServiceError(s.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const i={threadId:e.threadId,turnId:e.turnId,session:r,startedAt:(new Date).toISOString(),status:"running",interactionFailure:void 0};return this.activeTurns.set(e.threadId,i),this.activeTurnOwners.set(e.turnId,e.threadId),this.emit(e.threadId,e.turnId,{type:"turn.started"}),this.driveTurn(i,e.input.text).catch(()=>{}),{accepted:!0,turnId:e.turnId}})}async cancelTurn(e){return this.mutationRequests.run(e.requestId,i.turnCancel,e,()=>{this.assertOpen();const t=this.activeTurns.get(e.threadId);if(void 0===t||t.turnId!==e.turnId)throw new RuntimeServiceError(s.turnNotFound,`Turn "${e.turnId}" 不存在或已结束`);t.status="cancelling";let r=!1;try{this.cancelPendingApprovalsForTurn(e.turnId,{status:"cancelled",reason:"Turn 已由客户端取消"})}finally{r=t.session.cancel()}return{cancelling:r}})}async respondApproval(e){return this.mutationRequests.run(e.requestId,i.approvalRespond,e,()=>this.resolvePendingApproval(e,"approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}}))}resolvePendingApproval(e,t){this.assertOpen();const r=this.requirePendingApproval(e),n="approve"===t.decision?r.session.approve(e.approvalId):r.session.reject(e.approvalId,t.reason);if(this.pendingApprovals.delete(e.approvalId),!n)throw this.emitApprovalResolved(r,{status:"cancelled",reason:"审批已失效"}),new RuntimeServiceError(s.approvalNotFound,`Approval "${e.approvalId}" 已失效`);return this.emitApprovalResolved(r,"approve"===t.decision?{status:"resolved",decision:"approve"}:{status:"resolved",decision:"reject",...void 0!==t.reason?{reason:t.reason}:{}}),{resolved:!0}}cancelPendingApproval(e,t){const r=this.findPendingApproval(e);return void 0!==r&&(this.pendingApprovals.delete(e.approvalId),this.emitApprovalResolved(r,t),!0)}async failPendingApprovalInteraction(e,t,r={status:"cancelled",reason:t}){const s=this.findPendingApproval(e);if(void 0===s)return!1;const n=this.activeTurns.get(e.threadId);void 0!==n&&n.turnId===e.turnId&&(n.interactionFailure=t),this.pendingApprovals.delete(e.approvalId);try{this.emitApprovalResolved(s,r)}finally{s.session.cancel()}return!0}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:$(t)}}async close(){if(this.closing)return;this.closing=!0;for(const e of this.activeTurns.values())e.session.cancel();const e=[...this.sessions.values()];this.sessions.clear(),this.activeTurns.clear(),this.activeTurnOwners.clear(),this.settledTurnOwners.clear(),this.pendingApprovals.clear(),await Promise.allSettled(e.map(e=>e.close())),this.mutationRequests.clear()}assertOpen(){if(this.closing)throw new RuntimeServiceError(s.runtimeClosing,"Runtime 正在关闭",{retryable:!0})}requireThread(e){const t=this.store.getThread(e);if(void 0===t)throw new RuntimeServiceError(s.threadNotFound,`Thread "${e}" 不存在`);return t}async requireSession(e){this.assertOpen(),this.requireThread(e);const t=this.sessions.get(e);if(void 0!==t)return t;const r=await this.engine.resumeSession(e);return this.sessions.set(e,r),r}emit(e,t,r){const s={protocolVersion:a,runtimeInstanceId:this.runtimeInstanceId,sequence:this.sequence,timestamp:(new Date).toISOString(),threadId:e,...void 0!==t?{turnId:t}:{},event:r};this.sequence+=1;for(const e of this.listeners)try{e(s)}catch{}}async driveTurn(e,t){const r={streamId:void 0,text:"",terminalEvent:void 0};let s;try{for await(const s of e.session.send(t))this.projectSessionEvent(e,r,s);s=r.terminalEvent??{type:"turn.completed"}}catch(e){s=r.terminalEvent??{type:"turn.failed",stage:"execute",message:f(P(e))}}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e)}this.emitTurnTerminal(e,s)}findTurnOwner(e,t){const r=this.activeTurnOwners.get(e);if(void 0!==r)return r;const s=this.settledTurnOwners.get(e);return s===t&&(this.settledTurnOwners.delete(e),this.settledTurnOwners.set(e,s)),s}settleTurnOwner(e){if(this.activeTurnOwners.get(e.turnId)===e.threadId&&(this.activeTurnOwners.delete(e.turnId),!this.closing))for(this.settledTurnOwners.set(e.turnId,e.threadId);this.settledTurnOwners.size>this.idempotencyCacheEntries;){const e=this.settledTurnOwners.keys().next().value;if(void 0===e)return;this.settledTurnOwners.delete(e)}}projectSessionEvent(e,r,s){switch(s.type){case"message-start":{const s=m.parse(t());return r.streamId=s,r.text="",void this.emit(e.threadId,e.turnId,{type:"message.started",streamId:s})}case"text-delta":{const n=r.streamId??m.parse(t());return void 0===r.streamId&&(r.streamId=n,this.emit(e.threadId,e.turnId,{type:"message.started",streamId:n})),r.text+=s.delta,void this.emit(e.threadId,e.turnId,{type:"message.delta",streamId:n,delta:s.delta})}case"message-finish":{const n=r.streamId??m.parse(t());return this.emit(e.threadId,e.turnId,{type:"message.completed",streamId:n,text:f(s.text||r.text)}),void(r.terminalEvent??={type:"turn.completed"})}case"reasoning-delta":{const t=this.reasoningSummaryProjector?.(s.delta);return void(void 0!==t&&t.length>0&&this.emit(e.threadId,e.turnId,{type:"reasoning.summary.delta",reasoningId:s.reasoningId,delta:f(t)}))}case"tool-call":return void this.emit(e.threadId,e.turnId,{type:"tool.started",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,input:b(s.input,void 0)});case"tool-output-delta":return void this.emit(e.threadId,e.turnId,{type:"tool.output",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,stream:s.stream,delta:f(s.delta)});case"tool-result":return void this.emit(e.threadId,e.turnId,{type:"tool.completed",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,...void 0!==s.executionId?{operationId:l.parse(s.executionId)}:{},...void 0!==s.outcome?{outcome:this.projectToolOutcome(s.outcome)}:{},display:b(s.display,void 0)});case"confirmation-required":{const t=B(e.turnId,s);return this.pendingApprovals.set(t.id,{threadId:e.threadId,session:e.session,approval:t}),void this.emit(e.threadId,e.turnId,{type:"approval.required",approval:t})}case"turn-cancelled":r.terminalEvent??=void 0===e.interactionFailure?{type:"turn.cancelled",reason:s.reason,message:f(s.message)}:{type:"turn.failed",stage:"execute",message:f(e.interactionFailure)};break;case"error":r.terminalEvent??={type:"turn.failed",stage:s.stage,message:f(s.message)}}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:f(e.reason)}:{}}}findPendingApproval(e){const t=this.pendingApprovals.get(e.approvalId);if(void 0!==t&&t.threadId===e.threadId&&t.approval.turnId===e.turnId)return t}requirePendingApproval(e){const t=this.findPendingApproval(e);if(void 0===t)throw new RuntimeServiceError(s.approvalNotFound,`Approval "${e.approvalId}" 不存在或已失效`);return t}emitApprovalResolved(e,t){this.emit(e.threadId,e.approval.turnId,{type:"approval.resolved",approvalId:e.approval.id,resolution:t})}cancelPendingApprovalsForTurn(e,t){const r=[...this.pendingApprovals.values()].filter(t=>t.approval.turnId===e);let s;for(const e of r)try{this.cancelPendingApproval({threadId:e.threadId,turnId:e.approval.turnId,approvalId:e.approval.id},t)}catch(e){s??=e}if(void 0!==s)throw s}emitTurnTerminal(e,t){this.cancelPendingApprovalsForTurn(e.turnId,"turn.cancelled"===t.type&&"timeout"===t.reason?{status:"expired",reason:"Turn 已超时"}:{status:"cancelled",reason:`Turn 已终止:${t.type}`}),this.emit(e.threadId,e.turnId,t)}}
@@ -30,14 +30,17 @@ export interface BashToolDeps {
30
30
  }
31
31
  declare const bashToolInputSchema: z.ZodObject<{
32
32
  command: z.ZodString;
33
+ explanation: z.ZodOptional<z.ZodString>;
33
34
  workdir: z.ZodOptional<z.ZodString>;
34
35
  timeout_ms: z.ZodOptional<z.ZodNumber>;
35
36
  }, "strip", z.ZodTypeAny, {
36
37
  command: string;
38
+ explanation?: string | undefined;
37
39
  workdir?: string | undefined;
38
40
  timeout_ms?: number | undefined;
39
41
  }, {
40
42
  command: string;
43
+ explanation?: string | undefined;
41
44
  workdir?: string | undefined;
42
45
  timeout_ms?: number | undefined;
43
46
  }>;
@@ -1 +1 @@
1
- import{resolve as o}from"node:path";import{tool as t}from"ai";import{z as r}from"zod";import{CLASSIFICATION_ANNOTATIONS as e}from"../types/command-classification.js";import{runBashCommand as n}from"../bash/exec.js";import{withAutoApprovedShellEnv as i}from"../bash/clean-env.js";import{formatBashResult as s}from"../bash/format-result.js";import{isWithinWorkdirRoot as a}from"../bash/workdir.js";import{gateToolCall as l}from"./build-tools.js";import{ToolRegistry as m}from"./naming.js";import{TOOL_OUTCOME_KINDS as u,failedToolResult as c,toolResultToModelOutput as d}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as p,executeCoordinatedTool as f}from"./tool-execution-coordinator.js";export const BASH_TOOL_AGENT_NAME="roll";export const BASH_TOOL_NAME="bash";export const BASH_TOOL_ID="roll__bash";export const POWERSHELL_TOOL_NAME="powershell";export const POWERSHELL_TOOL_ID="roll__powershell";const w=256,k=4096,h="shell:opaque-side-effects";function _(o){return"known-safe"===o||"dangerous"===o?o:"unknown"}const b=r.object({command:r.string().min(1).describe("要执行的 shell 命令(单字符串,由当前 shell 后端执行)"),workdir:r.string().min(1).optional().describe("工作目录绝对路径,默认为 roll chat 当前目录。不要在 command 里用 cd,改用本字段"),timeout_ms:r.number().int().min(1).max(6e5).optional().describe("超时毫秒数。默认 10000,上限受 maxTimeoutMs 与 turnTimeoutMs 约束;长脚本请显式调大")});function x(o){return"object"==typeof o&&null!==o&&"command"in o&&!("string"!=typeof o.command||"workdir"in o&&void 0!==o.workdir&&"string"!=typeof o.workdir||"timeout_ms"in o&&void 0!==o.timeout_ms&&"number"!=typeof o.timeout_ms)}function y(o,t,r){const e=o.emitEvent;if(!e)return;let n=0;return(o,i)=>{if(n>=w)return;n+=1;const s=i.length>k?i.slice(0,k):i;e({type:"tool-output-delta",toolCallId:t,agentName:"roll",toolName:r,stream:o,delta:s})}}async function M(o,t,r,e){if(o.policy)return l(o,"roll",t,r,e);const n=await o.requestApproval({agentName:"roll",toolName:t,input:r,reason:"shell 命令需确认"});return n.approved?void 0:c(u.userRejected,"已取消执行"+(n.reason?`: ${n.reason}`:""),n.reason?{reason:n.reason}:{})}export function buildBashToolset(r,l,m,w={}){const k=r.profile.toolName,O=l.register("roll",k),g=w.classifier??r.profile,v=w.exec??n,T=r.env??process.env,j=t=>({workdir:o(r.workdir,t.workdir??"."),timeoutMs:Math.min(t.timeout_ms??r.defaultTimeoutMs,r.maxTimeoutMs,r.turnTimeoutMs)}),E=(o,t)=>{const{workdir:n,timeoutMs:i}=j(o),s=a(r.workdir,n)?t??g.classify(o.command,n):"unknown";return{workdir:n,timeoutMs:i,classification:s,annotations:e[s]}},S={prepare:async(o,t)=>{if(!x(o))return c(u.invalidInput,"参数校验失败: command 必须为字符串",{raw:o});const r=E(o,_(t));return M(m,k,{command:o.command,workdir:r.workdir,timeout_ms:r.timeoutMs},r.annotations)},resources:(o,t)=>{if(!x(o))return[{key:h,mode:p.write},{key:`shell:${r.workdir}`,mode:p.write}];const e=E(o,_(t)),n=!0===e.annotations.readOnlyHint&&!0!==e.annotations.destructiveHint,i={key:`shell:${e.workdir}`,mode:n?p.read:p.write};return n?[i]:[{key:h,mode:p.write},i]},captureExecutionState:o=>x(o)?E(o).classification:"unknown",revalidateExecution:(o,t)=>{if("known-safe"===t&&x(o))return"known-safe"===E(o).classification?void 0:c(u.toolFailed,"shell 命令的安全条件在准入后发生变化,已在执行前阻止;请重新提交以重新确认")}};return m.coordinator?.register(O,S),{[O]:t({description:"在当前 shell 后端中执行一条命令并返回输出。需确认的命令继承 roll 进程环境;自动批准的 known-safe 命令使用隔离的系统 PATH 与 shell 环境。总是用 workdir 参数设置工作目录,不要用 cd。",inputSchema:b,toModelOutput:({output:o})=>d(o),execute:async(o,t)=>{const{workdir:e,timeoutMs:n}=j(o);return f(m.coordinator,S,O,t.toolCallId,o,t.abortSignal,async a=>{const l=y(m,t.toolCallId,k),u=await v({command:o.command,workdir:e,timeoutMs:n,maxCaptureBytes:r.maxCaptureBytes,profile:r.profile,env:"known-safe"===a?i(T):T,...t.abortSignal?{abortSignal:t.abortSignal}:{},...l?{onDelta:l}:{}});return s({result:u,maxModelOutputChars:r.maxModelOutputChars})})}})}}
1
+ import{resolve as o}from"node:path";import{tool as t}from"ai";import{z as e}from"zod";import{CLASSIFICATION_ANNOTATIONS as r}from"../types/command-classification.js";import{runBashCommand as n}from"../bash/exec.js";import{withAutoApprovedShellEnv as i}from"../bash/clean-env.js";import{formatBashResult as a}from"../bash/format-result.js";import{isWithinWorkdirRoot as s}from"../bash/workdir.js";import{gateToolCall as l}from"./build-tools.js";import{ToolRegistry as m}from"./naming.js";import{TOOL_OUTCOME_KINDS as c,failedToolResult as u,toolResultToModelOutput as d}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as p,executeCoordinatedTool as f}from"./tool-execution-coordinator.js";import{shellCommandExplanationSchema as w}from"./shell-explanation.js";export const BASH_TOOL_AGENT_NAME="roll";export const BASH_TOOL_NAME="bash";export const BASH_TOOL_ID="roll__bash";export const POWERSHELL_TOOL_NAME="powershell";export const POWERSHELL_TOOL_ID="roll__powershell";const k=256,h=4096,x="shell:opaque-side-effects";function v(o){return"known-safe"===o||"dangerous"===o?o:"unknown"}const M=e.object({command:e.string().min(1).describe("要执行的 shell 命令(单字符串,由当前 shell 后端执行)"),explanation:w.optional(),workdir:e.string().min(1).optional().describe("工作目录绝对路径,默认为 roll chat 当前目录。不要在 command 里用 cd,改用本字段"),timeout_ms:e.number().int().min(1).max(6e5).optional().describe("超时毫秒数。默认 10000,上限受 maxTimeoutMs 与 turnTimeoutMs 约束;长脚本请显式调大")});function _(o){const t=M.safeParse(o);return t.success?t.data:void 0}function b(o,t,e){const r=o.emitEvent;if(!r)return;let n=0;return(o,i)=>{if(n>=k)return;n+=1;const a=i.length>h?i.slice(0,h):i;r({type:"tool-output-delta",toolCallId:t,agentName:"roll",toolName:e,stream:o,delta:a})}}async function y(o,t,e,r,n,i){if(o.policy)return l(o,"roll",t,e,n,{includePolicyReason:"dangerous"===r,...void 0!==i?{explanation:i}:{}});const a=await o.requestApproval({agentName:"roll",toolName:t,input:e,reason:"dangerous"===r?"破坏性操作":void 0,...void 0!==i?{explanation:i}:{}});return a.approved?void 0:u(c.userRejected,"已取消执行"+(a.reason?`: ${a.reason}`:""),a.reason?{reason:a.reason}:{})}export function buildBashToolset(e,l,m,w={}){const k=e.profile.toolName,h=l.register("roll",k),O=w.classifier??e.profile,g=w.exec??n,T=e.env??process.env,j=t=>({workdir:o(e.workdir,t.workdir??"."),timeoutMs:Math.min(t.timeout_ms??e.defaultTimeoutMs,e.maxTimeoutMs,e.turnTimeoutMs)}),E=(o,t)=>{const{workdir:n,timeoutMs:i}=j(o),a=s(e.workdir,n)?t??O.classify(o.command,n):"unknown";return{workdir:n,timeoutMs:i,classification:a,annotations:r[a]}},S={prepare:async(o,t)=>{const e=_(o);if(void 0===e)return u(c.invalidInput,"参数校验失败: command 必须为非空字符串,explanation 最多 100 字符",{raw:o});const r=E(e,v(t));return y(m,k,{command:e.command,workdir:r.workdir,timeout_ms:r.timeoutMs},r.classification,r.annotations,e.explanation)},resources:(o,t)=>{const r=_(o);if(void 0===r)return[{key:x,mode:p.write},{key:`shell:${e.workdir}`,mode:p.write}];const n=E(r,v(t)),i=!0===n.annotations.readOnlyHint&&!0!==n.annotations.destructiveHint,a={key:`shell:${n.workdir}`,mode:i?p.read:p.write};return i?[a]:[{key:x,mode:p.write},a]},captureExecutionState:o=>{const t=_(o);return void 0===t?"unknown":E(t).classification},revalidateExecution:(o,t)=>{const e=_(o);if("known-safe"===t&&void 0!==e)return"known-safe"===E(e).classification?void 0:u(c.toolFailed,"shell 命令的安全条件在准入后发生变化,已在执行前阻止;请重新提交以重新确认")}};return m.coordinator?.register(h,S),{[h]:t({description:"在当前 shell 后端中执行一条命令并返回输出。需确认的命令继承 roll 进程环境;自动批准的 known-safe 命令使用隔离的系统 PATH 与 shell 环境。总是用 workdir 参数设置工作目录,不要用 cd。",inputSchema:M,toModelOutput:({output:o})=>d(o),execute:async(o,t)=>{const{workdir:r,timeoutMs:n}=j(o);return f(m.coordinator,S,h,t.toolCallId,o,t.abortSignal,async s=>{const l=b(m,t.toolCallId,k),c=await g({command:o.command,workdir:r,timeoutMs:n,maxCaptureBytes:e.maxCaptureBytes,profile:e.profile,env:"known-safe"===s?i(T):T,...t.abortSignal?{abortSignal:t.abortSignal}:{},...l?{onDelta:l}:{}});return a({result:c,maxModelOutputChars:e.maxModelOutputChars})})}})}}
@@ -27,15 +27,22 @@ export interface ApprovalRequest {
27
27
  readonly toolName: string;
28
28
  readonly input: Record<string, unknown>;
29
29
  readonly reason: string | undefined;
30
+ readonly explanation?: string;
30
31
  }
31
32
  export interface ToolBridgeContext {
32
33
  readonly policy?: ToolPolicy;
33
34
  readonly requestApproval: (request: ApprovalRequest) => Promise<ApprovalDecision>;
34
35
  readonly coordinator?: ToolExecutionCoordinator;
35
36
  }
37
+ interface ApprovalDisplayOptions {
38
+ readonly explanation?: string;
39
+ /** False when a conservative policy label would overstate the actual user-visible risk. */
40
+ readonly includePolicyReason?: boolean;
41
+ }
36
42
  export interface BuiltToolset {
37
43
  readonly tools: ToolSet;
38
44
  readonly registry: ToolRegistry;
39
45
  }
40
- export declare function gateToolCall(ctx: ToolBridgeContext, agentName: string, toolName: string, input: Record<string, unknown>, annotations: ToolAnnotations | undefined): Promise<NormalizedToolResult | undefined>;
46
+ export declare function gateToolCall(ctx: ToolBridgeContext, agentName: string, toolName: string, input: Record<string, unknown>, annotations: ToolAnnotations | undefined, display?: ApprovalDisplayOptions): Promise<NormalizedToolResult | undefined>;
41
47
  export declare function buildAgentToolset(sources: readonly AgentToolSource[], ctx: ToolBridgeContext, registry?: ToolRegistry): BuiltToolset;
48
+ export {};
@@ -1 +1 @@
1
- import{lstatSync as t,readdirSync as e,readlinkSync as n,realpathSync as o,statSync as r}from"node:fs";import{basename as i,dirname as s,isAbsolute as a,resolve as c}from"node:path";import{jsonSchema as u,tool as f}from"ai";import{preflightToolCall as l}from"@roll-agent/core/tool-runtime/preflight";import{ToolRegistry as d}from"./naming.js";import{TOOL_OUTCOME_KINDS as p,failedToolResult as m,normalizeToolResult as g,toolResultToModelOutput as v}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as y,TOOL_RESOURCE_HINT_KINDS as h,executeCoordinatedTool as S}from"./tool-execution-coordinator.js";export const ROLL_RESOURCE_HINTS_META_KEY="roll/resourceHints";function $(t){return"object"!=typeof t||null===t||Array.isArray(t)?{}:t}function b(t){return`参数校验失败: ${t.map(t=>t.message).join("; ")}`}function w(t){return!0===t?.readOnlyHint&&!0!==t.destructiveHint?y.read:y.write}function C(t){return"string"==typeof t||"number"==typeof t?[t]:Array.isArray(t)&&0!==t.length&&t.every(t=>"string"==typeof t||"number"==typeof t)?t:void 0}const N=256,k=new Map;function A(t,e){for(let n=0;n<t.length;n+=1){const o=t.charCodeAt(n),r=o>=65&&o<=90?String.fromCharCode(o+32):o>=97&&o<=122?String.fromCharCode(o-32):void 0;if(void 0===r)continue;const i=`${t.slice(0,n)}${r}${t.slice(n+1)}`;if(!e.has(i))return i}}function O(t){return"object"==typeof t&&null!==t&&"code"in t?t.code:void 0}function j(e,n,o){const r=A(n,o);if(void 0!==r)try{const o=t(c(e,n)),i=t(c(e,r));return o.dev===i.dev&&o.ino===i.ino}catch(t){const e=O(t);return"ENOENT"!==e&&"ENOTDIR"!==e&&void 0}}function T(t,e){if(k.size>=N){const t=k.keys().next().value;void 0!==t&&k.delete(t)}return k.set(t,e),e}function E(n){const o=k.get(n);if(void 0!==o)return o;let r;try{r=t(n).dev}catch{return T(n,!0)}let i=n;for(;;){const o=k.get(i);if(void 0!==o)return T(n,o);try{const t=e(i),o=new Set(t);for(const e of t){const t=j(i,e,o);if(void 0!==t)return T(i,t),T(n,t)}}catch{}const a=s(i);if(a===i)break;try{if(t(a).dev!==r)break}catch{break}i=a}return T(n,!0)}function x(t){let e=t.normalize("NFC");for(let t=0;t<8;t+=1){const t=e.toUpperCase().toLowerCase().normalize("NFC");if(t===e)return e;e=t}return e}function z(t,e){try{const n=r(t,{bigint:!0}),o=e.map(t=>x(t));return`file-unresolved-alias:${n.dev.toString()}:${n.ino.toString()}:${JSON.stringify(o)}`}catch{return}}function H(e,r=new Set){let a=e;const u=[];for(;;)try{const t=o.native(a),e=u.reverse().map(t=>t.normalize("NFC"));if(0===e.length)return{path:t};const n=E(t),r=c(t,...n?e.map(t=>t.toLowerCase()):e),i=n?z(t,e):void 0;return{path:r,...void 0===i?{}:{unresolvedAliasKey:i}}}catch{try{if(t(a).isSymbolicLink()&&!r.has(a)){const t=new Set(r);t.add(a);const e=n(a);return H(c(s(a),e,...[...u].reverse()),t)}}catch{}const o=s(a);if(o===a)return{path:e};u.push(i(a)),a=o}}function L(t){try{const e=r(t,{bigint:!0});return`file-inode:${e.dev.toString()}:${e.ino.toString()}`}catch{return}}function R(t,e,n){const o=String(e).trim();if(0!==o.length)switch(t.kind){case h.file:{if(!a(o)&&void 0===n)return;const t=H(c(n??"/",o)),e=L(t.path);return[`file:${t.path}`,...void 0===t.unresolvedAliasKey?[]:[t.unresolvedAliasKey],...void 0===e?[]:[e]]}case h.browserSession:return[`browser-session:${o}`];case h.conversation:return[`conversation:${o}`];case h.custom:{const e=t.namespace?.trim();return e?[`${e}:${o}`]:void 0}}}function I(t,e,n,o,r){const i=w(o),s=$(n),a=[{key:`agent:${t}`,mode:i}],c=[];for(const t of r??[]){if(!Object.hasOwn(s,t.field))continue;const n=C(s[t.field]);if(void 0===n)return a;for(const o of n){const n=R(t,o,e);if(void 0===n||0===n.length)return a;c.push(...n.map(e=>({key:e,mode:t.mode??i})))}}return 0===c.length?a:[{key:`agent:${t}`,mode:y.read},...c]}export async function gateToolCall(t,e,n,o,r){if(!t.policy)return;const i=t.policy.check({agentName:e,toolName:n,input:o,...r?{annotations:r}:{}});if("deny"===i.action)return m(p.policyDenied,"策略拒绝执行"+(i.reason?`: ${i.reason}`:""),i.reason?{reason:i.reason}:{});if("confirm"===i.action){const r=await t.requestApproval({agentName:e,toolName:n,input:o,reason:i.reason});if(!r.approved)return m(p.userRejected,"已取消执行"+(r.reason?`: ${r.reason}`:""),r.reason?{reason:r.reason}:{})}}export function buildAgentToolset(t,e,n=new d){const o={};for(const r of t){const{client:t,agentName:i,agentSource:s,transport:a,runtimeOwnership:c,resourceBaseDir:d}=r;for(const{tool:y,annotations:h,resourceHints:w}of r.tools){const r=n.register(i,y.name,{...s?{agentSource:s}:{},...a?{transport:a}:{},...c?{runtimeOwnership:c}:{},...h?{annotations:h}:{}}),C={prepare:async t=>{const n=$(t),o=l(y,n);return o.ok?gateToolCall(e,i,y.name,n,h):m(p.invalidInput,b(o.issues),{raw:o.issues})},resources:t=>I(i,d,t,h,w)};e.coordinator?.register(r,C),o[r]=f({description:y.description??`${y.name} (via ${i})`,inputSchema:u(y.inputSchema),toModelOutput:({output:t})=>v(t),execute:async(n,o)=>{const i=$(n);return S(e.coordinator,C,r,o.toolCallId,i,o.abortSignal,async()=>{const e=o.abortSignal?{signal:o.abortSignal}:void 0,n=await t.callTool({name:y.name,arguments:i},void 0,e);return g(n)})}})}}return{tools:o,registry:n}}
1
+ import{lstatSync as t,readdirSync as n,readlinkSync as e,realpathSync as o,statSync as r}from"node:fs";import{basename as i,dirname as s,isAbsolute as a,resolve as c}from"node:path";import{jsonSchema as u,tool as l}from"ai";import{preflightToolCall as f}from"@roll-agent/core/tool-runtime/preflight";import{ToolRegistry as d}from"./naming.js";import{TOOL_OUTCOME_KINDS as p,failedToolResult as m,normalizeToolResult as v,toolResultToModelOutput as g}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as y,TOOL_RESOURCE_HINT_KINDS as h,executeCoordinatedTool as S}from"./tool-execution-coordinator.js";export const ROLL_RESOURCE_HINTS_META_KEY="roll/resourceHints";function $(t){return"object"!=typeof t||null===t||Array.isArray(t)?{}:t}function b(t){return`参数校验失败: ${t.map(t=>t.message).join("; ")}`}function w(t){return!0===t?.readOnlyHint&&!0!==t.destructiveHint?y.read:y.write}function C(t){return"string"==typeof t||"number"==typeof t?[t]:Array.isArray(t)&&0!==t.length&&t.every(t=>"string"==typeof t||"number"==typeof t)?t:void 0}const N=256,k=new Map;function A(t,n){for(let e=0;e<t.length;e+=1){const o=t.charCodeAt(e),r=o>=65&&o<=90?String.fromCharCode(o+32):o>=97&&o<=122?String.fromCharCode(o-32):void 0;if(void 0===r)continue;const i=`${t.slice(0,e)}${r}${t.slice(e+1)}`;if(!n.has(i))return i}}function O(t){return"object"==typeof t&&null!==t&&"code"in t?t.code:void 0}function x(n,e,o){const r=A(e,o);if(void 0!==r)try{const o=t(c(n,e)),i=t(c(n,r));return o.dev===i.dev&&o.ino===i.ino}catch(t){const n=O(t);return"ENOENT"!==n&&"ENOTDIR"!==n&&void 0}}function j(t,n){if(k.size>=N){const t=k.keys().next().value;void 0!==t&&k.delete(t)}return k.set(t,n),n}function T(e){const o=k.get(e);if(void 0!==o)return o;let r;try{r=t(e).dev}catch{return j(e,!0)}let i=e;for(;;){const o=k.get(i);if(void 0!==o)return j(e,o);try{const t=n(i),o=new Set(t);for(const n of t){const t=x(i,n,o);if(void 0!==t)return j(i,t),j(e,t)}}catch{}const a=s(i);if(a===i)break;try{if(t(a).dev!==r)break}catch{break}i=a}return j(e,!0)}function E(t){let n=t.normalize("NFC");for(let t=0;t<8;t+=1){const t=n.toUpperCase().toLowerCase().normalize("NFC");if(t===n)return n;n=t}return n}function R(t,n){try{const e=r(t,{bigint:!0}),o=n.map(t=>E(t));return`file-unresolved-alias:${e.dev.toString()}:${e.ino.toString()}:${JSON.stringify(o)}`}catch{return}}function z(n,r=new Set){let a=n;const u=[];for(;;)try{const t=o.native(a),n=u.reverse().map(t=>t.normalize("NFC"));if(0===n.length)return{path:t};const e=T(t),r=c(t,...e?n.map(t=>t.toLowerCase()):n),i=e?R(t,n):void 0;return{path:r,...void 0===i?{}:{unresolvedAliasKey:i}}}catch{try{if(t(a).isSymbolicLink()&&!r.has(a)){const t=new Set(r);t.add(a);const n=e(a);return z(c(s(a),n,...[...u].reverse()),t)}}catch{}const o=s(a);if(o===a)return{path:n};u.push(i(a)),a=o}}function H(t){try{const n=r(t,{bigint:!0});return`file-inode:${n.dev.toString()}:${n.ino.toString()}`}catch{return}}function L(t,n,e){const o=String(n).trim();if(0!==o.length)switch(t.kind){case h.file:{if(!a(o)&&void 0===e)return;const t=z(c(e??"/",o)),n=H(t.path);return[`file:${t.path}`,...void 0===t.unresolvedAliasKey?[]:[t.unresolvedAliasKey],...void 0===n?[]:[n]]}case h.browserSession:return[`browser-session:${o}`];case h.conversation:return[`conversation:${o}`];case h.custom:{const n=t.namespace?.trim();return n?[`${n}:${o}`]:void 0}}}function I(t,n,e,o,r){const i=w(o),s=$(e),a=[{key:`agent:${t}`,mode:i}],c=[];for(const t of r??[]){if(!Object.hasOwn(s,t.field))continue;const e=C(s[t.field]);if(void 0===e)return a;for(const o of e){const e=L(t,o,n);if(void 0===e||0===e.length)return a;c.push(...e.map(n=>({key:n,mode:t.mode??i})))}}return 0===c.length?a:[{key:`agent:${t}`,mode:y.read},...c]}export async function gateToolCall(t,n,e,o,r,i){if(!t.policy)return;const s=t.policy.check({agentName:n,toolName:e,input:o,...r?{annotations:r}:{}});if("deny"===s.action)return m(p.policyDenied,"策略拒绝执行"+(s.reason?`: ${s.reason}`:""),s.reason?{reason:s.reason}:{});if("confirm"===s.action){const r=await t.requestApproval({agentName:n,toolName:e,input:o,reason:!1===i?.includePolicyReason?void 0:s.reason,...void 0!==i?.explanation?{explanation:i.explanation}:{}});if(!r.approved)return m(p.userRejected,"已取消执行"+(r.reason?`: ${r.reason}`:""),r.reason?{reason:r.reason}:{})}}export function buildAgentToolset(t,n,e=new d){const o={};for(const r of t){const{client:t,agentName:i,agentSource:s,transport:a,runtimeOwnership:c,resourceBaseDir:d}=r;for(const{tool:y,annotations:h,resourceHints:w}of r.tools){const r=e.register(i,y.name,{...s?{agentSource:s}:{},...a?{transport:a}:{},...c?{runtimeOwnership:c}:{},...h?{annotations:h}:{}}),C={prepare:async t=>{const e=$(t),o=f(y,e);return o.ok?gateToolCall(n,i,y.name,e,h):m(p.invalidInput,b(o.issues),{raw:o.issues})},resources:t=>I(i,d,t,h,w)};n.coordinator?.register(r,C),o[r]=l({description:y.description??`${y.name} (via ${i})`,inputSchema:u(y.inputSchema),toModelOutput:({output:t})=>g(t),execute:async(e,o)=>{const i=$(e);return S(n.coordinator,C,r,o.toolCallId,i,o.abortSignal,async()=>{const n=o.abortSignal?{signal:o.abortSignal}:void 0,e=await t.callTool({name:y.name,arguments:i},void 0,n);return v(e)})}})}}return{tools:o,registry:e}}
@@ -7,12 +7,27 @@ export declare const TOOL_OUTCOME_KINDS: {
7
7
  readonly cancelled: "cancelled";
8
8
  readonly toolFailed: "tool_failed";
9
9
  };
10
+ export declare const TOOL_CANCELLATION_EXECUTION_STATES: {
11
+ readonly notExecuted: "not_executed";
12
+ readonly outcomeUnknown: "outcome_unknown";
13
+ };
10
14
  export type ToolOutcomeKind = (typeof TOOL_OUTCOME_KINDS)[keyof typeof TOOL_OUTCOME_KINDS];
15
+ export type ToolCancellationExecutionState = (typeof TOOL_CANCELLATION_EXECUTION_STATES)[keyof typeof TOOL_CANCELLATION_EXECUTION_STATES];
11
16
  export type ToolOutcome = {
12
17
  readonly kind: typeof TOOL_OUTCOME_KINDS.success;
13
18
  } | {
14
- readonly kind: typeof TOOL_OUTCOME_KINDS.userRejected | typeof TOOL_OUTCOME_KINDS.policyDenied | typeof TOOL_OUTCOME_KINDS.invalidInput | typeof TOOL_OUTCOME_KINDS.cancelled | typeof TOOL_OUTCOME_KINDS.toolFailed;
19
+ readonly kind: typeof TOOL_OUTCOME_KINDS.userRejected | typeof TOOL_OUTCOME_KINDS.policyDenied | typeof TOOL_OUTCOME_KINDS.invalidInput | typeof TOOL_OUTCOME_KINDS.toolFailed;
20
+ readonly reason?: string;
21
+ } | {
22
+ readonly kind: typeof TOOL_OUTCOME_KINDS.cancelled;
15
23
  readonly reason?: string;
24
+ /**
25
+ * Durable execution fact recorded when a Turn is interrupted.
26
+ *
27
+ * Missing means execution state was not captured, including legacy records and
28
+ * cancellations outside interrupted-Turn persistence.
29
+ */
30
+ readonly executionState?: ToolCancellationExecutionState;
16
31
  };
17
32
  export type ToolModelContentPart = {
18
33
  type: "text";
@@ -71,6 +86,7 @@ export declare function createToolResult(outcome: ToolOutcome, display: unknown,
71
86
  export declare function successfulToolResult(display: unknown, options?: Omit<ToolResultOptions, "reason">): NormalizedToolResult;
72
87
  export declare function failedToolResult(kind: Exclude<ToolOutcomeKind, typeof TOOL_OUTCOME_KINDS.success>, display: unknown, options?: ToolResultOptions): NormalizedToolResult;
73
88
  export declare function normalizeToolResult(result: unknown): NormalizedToolResult;
89
+ export declare function isToolCancellationExecutionState(value: unknown): value is ToolCancellationExecutionState;
74
90
  export declare function isNormalizedToolResult(value: unknown): value is NormalizedToolResult;
75
91
  export declare function readToolOutcome(value: unknown): ToolOutcome;
76
92
  export declare function readIsError(value: unknown): boolean;
@@ -1 +1 @@
1
- import{extractTextContent as t,isToolErrorResult as e}from"@roll-agent/core/cli/utils/tool-results";export const TOOL_OUTCOME_KINDS={success:"success",userRejected:"user_rejected",policyDenied:"policy_denied",invalidInput:"invalid_input",cancelled:"cancelled",toolFailed:"tool_failed"};const n=16e3,r=64,o=6e4,i=128,u="\n\n[工具模型内容已截断;完整结果仍保留在 raw 视图]";function s(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function l(t){if(null===t||"string"==typeof t||"boolean"==typeof t||"number"==typeof t&&Number.isFinite(t))return t;if(Array.isArray(t))return t.map(l);if(s(t)){const e={};for(const[n,r]of Object.entries(t))e[n]=l(r);return e}return void 0===t?null:String(t)}function c(t){if(!t)return!1;const e=t.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(t=>t.length>0);return 1===e.length&&["data","image","audio"].includes(e[0]??"")||e.some(t=>["base64","blob","bytes"].includes(t))||e.includes("data")&&e.some(t=>["image","audio","binary"].includes(t))}function a(t){return`[binary content omitted: ${String(t.length)} chars]`}function p(t,e,n){return t.length<=e?t:e<=n.length?n.slice(0,e):`${t.slice(0,e-n.length)}${n}`}function d(t,e,n=0){if(n>16)return"[nested value omitted]";if("string"==typeof t)return c(e)||/^data:[^,]+;base64,/iu.test(t)?a(t):p(t,o,u);if(null===t||"boolean"==typeof t||"number"==typeof t)return Number.isFinite(t)||"number"!=typeof t?t:String(t);if(Array.isArray(t)){const e=t.slice(0,i).map(t=>d(t,void 0,n+1));return t.length>i&&e.push(`[${String(t.length-i)} items omitted]`),e}if(s(t)){const e={},r=Object.entries(t).filter(([t])=>"_meta"!==t);for(const[t,o]of r.slice(0,i))e[t]=d(o,t,n+1);return r.length>i&&(e["[omitted]"]=`${String(r.length-i)} fields`),e}return void 0===t?null:String(t)}function f(t,e,o=0){if(o>12)return"[nested value omitted]";if("string"==typeof t)return c(e)||/^data:[^,]+;base64,/iu.test(t)?a(t):t.length<=n?t:g(t);if(null===t||"boolean"==typeof t||"number"==typeof t)return Number.isFinite(t)||"number"!=typeof t?t:String(t);if(Array.isArray(t)){const e=t.slice(0,r).map(t=>f(t,void 0,o+1));return t.length>r&&e.push(`[${String(t.length-r)} items omitted]`),e}if(s(t)){const e={},n=Object.entries(t).filter(([t])=>"_meta"!==t);for(const[t,i]of n.slice(0,r))e[t]=f(i,t,o+1);return n.length>r&&(e["[omitted]"]=`${String(n.length-r)} fields`),e}return void 0===t?null:String(t)}function y(t){return"string"==typeof t?t:JSON.stringify(l(t),null,2)}function g(t){return t.length<=n?t:`${t.slice(0,n)}\n\n[工具展示内容已截断]`}function m(t,e=!1){const n=d(t),r=y(n);if(r.length<=o)return e?{type:"error-json",value:n}:{type:"json",value:n};const i=p(r,o,u);return e?{type:"error-text",value:i}:{type:"text",value:i}}function O(t,e=!1){const n=p(t,o,u);return e?{type:"error-text",value:n}:{type:"text",value:n}}function T(t){const e=[];let n=o;for(const r of t.slice(0,i)){if(n<=0)break;if("text"===r.type){const t=p(r.text,n,u);e.push({type:"text",text:t}),n-=t.length;continue}const t=r.data.data.length;if(t<=n){e.push(r),n-=t;continue}const o=p(`[${r.mediaType} file omitted from model projection: ${String(t)} chars]`,n,u);e.push({type:"text",text:o}),n-=o.length}if(t.length>i&&n>0){const r=p(`[${String(t.length-i)} model content parts omitted]`,n,u);e.push({type:"text",text:r})}return e}function h(t,e){return e.kind===TOOL_OUTCOME_KINDS.userRejected?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:e.kind===TOOL_OUTCOME_KINDS.success?"string"==typeof t?O(t):m(t):m({code:e.kind,message:"string"==typeof t?t:y(t),...void 0!==e.reason?{reason:e.reason}:{}},!0)}export function createToolResult(t,e,n={}){const r=t.kind!==TOOL_OUTCOME_KINDS.success;return{raw:Object.prototype.hasOwnProperty.call(n,"raw")?n.raw:e,model:n.model??h(e,t),display:e,outcome:t,output:e,isError:r}}export function successfulToolResult(t,e={}){return createToolResult({kind:TOOL_OUTCOME_KINDS.success},t,e)}export function failedToolResult(t,e,n={}){return createToolResult({kind:t,...void 0!==n.reason?{reason:n.reason}:{}},e,n)}function x(t){return s(t)?t.content:void 0}function _(t){return s(t)?t.structuredContent:void 0}function v(t){return s(t)&&"toolResult"in t?{value:t.toolResult,wrapped:!0}:{value:t,wrapped:!1}}function b(t){if("string"==typeof t.data&&"string"==typeof t.mimeType)return{type:"file",data:{type:"data",data:t.data},mediaType:t.mimeType}}function S(t){const e=s(t.resource)?t.resource:void 0;if(!e)return;const n="string"==typeof e.uri?e.uri:"embedded resource";return"string"==typeof e.text?{type:"text",text:`[${n}]\n${e.text}`}:"string"==typeof e.blob?{type:"file",data:{type:"data",data:e.blob},mediaType:"string"==typeof e.mimeType?e.mimeType:"application/octet-stream",filename:n}:void 0}function $(t){if("string"!=typeof t.uri)return;const e="string"==typeof t.name?t.name:"resource",n="string"==typeof t.description?` — ${t.description}`:"";return{type:"text",text:`[${e}] ${t.uri}${n}`}}function N(t){if(s(t)&&"string"==typeof t.type)return"text"===t.type&&"string"==typeof t.text?{type:"text",text:t.text}:"image"===t.type||"audio"===t.type?b(t):"resource"===t.type?S(t):"resource_link"===t.type?$(t):void 0}function k(t,e,n,r){if(n)return h(e,{kind:TOOL_OUTCOME_KINDS.toolFailed});const o=x(t),i=Array.isArray(o)?o.flatMap(t=>{const e=N(t);return void 0===e?[]:[e]}):[],u=_(t);return void 0!==u&&i.length>0&&i.push({type:"text",text:`[structuredContent]\n${y(d(u))}`}),i.length>0?{type:"content",value:T(i)}:void 0!==u?m(u):r?"string"==typeof t?O(t):m(t):h(e,{kind:TOOL_OUTCOME_KINDS.success})}function M(e,o,i){const u=t(x(e));if(u.length>0)return g(u.join("\n"));const l=_(e);if(void 0!==l){const t=f(l),e=y(t);return e.length<=n?t:g(e)}const c=x(e);if(Array.isArray(c)&&c.length>0){const t=c.slice(0,r).flatMap(t=>{if(!s(t)||"string"!=typeof t.type)return[];const e="string"==typeof t.mimeType?` ${t.mimeType}`:"";return[`[${t.type}${e}]`]});if(t.length>0){const e=c.length-Math.min(c.length,r);return`${t.join("\n")}${e>0?`\n[另有 ${String(e)} 项未展示]`:""}`}}if(i){const t=f(e),r=y(t);return r.length<=n?t:g(r)}return o?"工具执行失败(无可展示的文本结果)":"工具执行完成(无可展示的文本结果)"}export function normalizeToolResult(t){const n=v(t),r=e(n.value),o=M(n.value,r,n.wrapped);return createToolResult(r?{kind:TOOL_OUTCOME_KINDS.toolFailed}:{kind:TOOL_OUTCOME_KINDS.success},o,{raw:t,model:k(n.value,o,r,n.wrapped)})}function C(t){return!(!s(t)||"string"!=typeof t.kind)&&Object.values(TOOL_OUTCOME_KINDS).some(e=>e===t.kind)}export function isNormalizedToolResult(t){return s(t)&&"raw"in t&&"model"in t&&"display"in t&&C(t.outcome)}export function readToolOutcome(t){return isNormalizedToolResult(t)?t.outcome:s(t)&&"execution-denied"===t.type?{kind:TOOL_OUTCOME_KINDS.userRejected,..."string"==typeof t.reason?{reason:t.reason}:{}}:s(t)&&"string"==typeof t.type&&t.type.startsWith("error")||E(t)?{kind:TOOL_OUTCOME_KINDS.toolFailed}:{kind:TOOL_OUTCOME_KINDS.success}}function E(t){return s(t)&&!0===t.isError}export function readIsError(t){return readToolOutcome(t).kind!==TOOL_OUTCOME_KINDS.success}export function readDisplayOutput(t){return isNormalizedToolResult(t)?t.display:s(t)&&"output"in t?t.output:t}export function toolResultToModelOutput(t){return t.model}
1
+ import{extractTextContent as t,isToolErrorResult as e}from"@roll-agent/core/cli/utils/tool-results";export const TOOL_OUTCOME_KINDS={success:"success",userRejected:"user_rejected",policyDenied:"policy_denied",invalidInput:"invalid_input",cancelled:"cancelled",toolFailed:"tool_failed"};export const TOOL_CANCELLATION_EXECUTION_STATES={notExecuted:"not_executed",outcomeUnknown:"outcome_unknown"};const n=16e3,o=64,r=6e4,i=128,u="\n\n[工具模型内容已截断;完整结果仍保留在 raw 视图]";function s(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function l(t){if(null===t||"string"==typeof t||"boolean"==typeof t||"number"==typeof t&&Number.isFinite(t))return t;if(Array.isArray(t))return t.map(l);if(s(t)){const e={};for(const[n,o]of Object.entries(t))e[n]=l(o);return e}return void 0===t?null:String(t)}function c(t){if(!t)return!1;const e=t.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(t=>t.length>0);return 1===e.length&&["data","image","audio"].includes(e[0]??"")||e.some(t=>["base64","blob","bytes"].includes(t))||e.includes("data")&&e.some(t=>["image","audio","binary"].includes(t))}function a(t){return`[binary content omitted: ${String(t.length)} chars]`}function p(t,e,n){return t.length<=e?t:e<=n.length?n.slice(0,e):`${t.slice(0,e-n.length)}${n}`}function d(t,e,n=0){if(n>16)return"[nested value omitted]";if("string"==typeof t)return c(e)||/^data:[^,]+;base64,/iu.test(t)?a(t):p(t,r,u);if(null===t||"boolean"==typeof t||"number"==typeof t)return Number.isFinite(t)||"number"!=typeof t?t:String(t);if(Array.isArray(t)){const e=t.slice(0,i).map(t=>d(t,void 0,n+1));return t.length>i&&e.push(`[${String(t.length-i)} items omitted]`),e}if(s(t)){const e={},o=Object.entries(t).filter(([t])=>"_meta"!==t);for(const[t,r]of o.slice(0,i))e[t]=d(r,t,n+1);return o.length>i&&(e["[omitted]"]=`${String(o.length-i)} fields`),e}return void 0===t?null:String(t)}function f(t,e,r=0){if(r>12)return"[nested value omitted]";if("string"==typeof t)return c(e)||/^data:[^,]+;base64,/iu.test(t)?a(t):t.length<=n?t:O(t);if(null===t||"boolean"==typeof t||"number"==typeof t)return Number.isFinite(t)||"number"!=typeof t?t:String(t);if(Array.isArray(t)){const e=t.slice(0,o).map(t=>f(t,void 0,r+1));return t.length>o&&e.push(`[${String(t.length-o)} items omitted]`),e}if(s(t)){const e={},n=Object.entries(t).filter(([t])=>"_meta"!==t);for(const[t,i]of n.slice(0,o))e[t]=f(i,t,r+1);return n.length>o&&(e["[omitted]"]=`${String(n.length-o)} fields`),e}return void 0===t?null:String(t)}function y(t){return"string"==typeof t?t:JSON.stringify(l(t),null,2)}function O(t){return t.length<=n?t:`${t.slice(0,n)}\n\n[工具展示内容已截断]`}function g(t,e=!1){const n=d(t),o=y(n);if(o.length<=r)return e?{type:"error-json",value:n}:{type:"json",value:n};const i=p(o,r,u);return e?{type:"error-text",value:i}:{type:"text",value:i}}function m(t,e=!1){const n=p(t,r,u);return e?{type:"error-text",value:n}:{type:"text",value:n}}function T(t){const e=[];let n=r;for(const o of t.slice(0,i)){if(n<=0)break;if("text"===o.type){const t=p(o.text,n,u);e.push({type:"text",text:t}),n-=t.length;continue}const t=o.data.data.length;if(t<=n){e.push(o),n-=t;continue}const r=p(`[${o.mediaType} file omitted from model projection: ${String(t)} chars]`,n,u);e.push({type:"text",text:r}),n-=r.length}if(t.length>i&&n>0){const o=p(`[${String(t.length-i)} model content parts omitted]`,n,u);e.push({type:"text",text:o})}return e}function x(t,e){return e.kind===TOOL_OUTCOME_KINDS.userRejected?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:e.kind===TOOL_OUTCOME_KINDS.success?"string"==typeof t?m(t):g(t):g({code:e.kind,message:"string"==typeof t?t:y(t),...void 0!==e.reason?{reason:e.reason}:{}},!0)}export function createToolResult(t,e,n={}){const o=t.kind!==TOOL_OUTCOME_KINDS.success;return{raw:Object.prototype.hasOwnProperty.call(n,"raw")?n.raw:e,model:n.model??x(e,t),display:e,outcome:t,output:e,isError:o}}export function successfulToolResult(t,e={}){return createToolResult({kind:TOOL_OUTCOME_KINDS.success},t,e)}export function failedToolResult(t,e,n={}){return createToolResult({kind:t,...void 0!==n.reason?{reason:n.reason}:{}},e,n)}function _(t){return s(t)?t.content:void 0}function h(t){return s(t)?t.structuredContent:void 0}function v(t){return s(t)&&"toolResult"in t?{value:t.toolResult,wrapped:!0}:{value:t,wrapped:!1}}function S(t){if("string"==typeof t.data&&"string"==typeof t.mimeType)return{type:"file",data:{type:"data",data:t.data},mediaType:t.mimeType}}function b(t){const e=s(t.resource)?t.resource:void 0;if(!e)return;const n="string"==typeof e.uri?e.uri:"embedded resource";return"string"==typeof e.text?{type:"text",text:`[${n}]\n${e.text}`}:"string"==typeof e.blob?{type:"file",data:{type:"data",data:e.blob},mediaType:"string"==typeof e.mimeType?e.mimeType:"application/octet-stream",filename:n}:void 0}function E(t){if("string"!=typeof t.uri)return;const e="string"==typeof t.name?t.name:"resource",n="string"==typeof t.description?` — ${t.description}`:"";return{type:"text",text:`[${e}] ${t.uri}${n}`}}function N(t){if(s(t)&&"string"==typeof t.type)return"text"===t.type&&"string"==typeof t.text?{type:"text",text:t.text}:"image"===t.type||"audio"===t.type?S(t):"resource"===t.type?b(t):"resource_link"===t.type?E(t):void 0}function C(t,e,n,o){if(n)return x(e,{kind:TOOL_OUTCOME_KINDS.toolFailed});const r=_(t),i=Array.isArray(r)?r.flatMap(t=>{const e=N(t);return void 0===e?[]:[e]}):[],u=h(t);return void 0!==u&&i.length>0&&i.push({type:"text",text:`[structuredContent]\n${y(d(u))}`}),i.length>0?{type:"content",value:T(i)}:void 0!==u?g(u):o?"string"==typeof t?m(t):g(t):x(e,{kind:TOOL_OUTCOME_KINDS.success})}function $(e,r,i){const u=t(_(e));if(u.length>0)return O(u.join("\n"));const l=h(e);if(void 0!==l){const t=f(l),e=y(t);return e.length<=n?t:O(e)}const c=_(e);if(Array.isArray(c)&&c.length>0){const t=c.slice(0,o).flatMap(t=>{if(!s(t)||"string"!=typeof t.type)return[];const e="string"==typeof t.mimeType?` ${t.mimeType}`:"";return[`[${t.type}${e}]`]});if(t.length>0){const e=c.length-Math.min(c.length,o);return`${t.join("\n")}${e>0?`\n[另有 ${String(e)} 项未展示]`:""}`}}if(i){const t=f(e),o=y(t);return o.length<=n?t:O(o)}return r?"工具执行失败(无可展示的文本结果)":"工具执行完成(无可展示的文本结果)"}export function normalizeToolResult(t){const n=v(t),o=e(n.value),r=$(n.value,o,n.wrapped);return createToolResult(o?{kind:TOOL_OUTCOME_KINDS.toolFailed}:{kind:TOOL_OUTCOME_KINDS.success},r,{raw:t,model:C(n.value,r,o,n.wrapped)})}export function isToolCancellationExecutionState(t){return Object.values(TOOL_CANCELLATION_EXECUTION_STATES).some(e=>e===t)}function L(t){return!(!s(t)||"string"!=typeof t.kind)&&(!!Object.values(TOOL_OUTCOME_KINDS).some(e=>e===t.kind)&&((void 0===t.reason||"string"==typeof t.reason)&&(t.kind!==TOOL_OUTCOME_KINDS.cancelled?void 0===t.executionState:void 0===t.executionState||isToolCancellationExecutionState(t.executionState))))}export function isNormalizedToolResult(t){return s(t)&&"raw"in t&&"model"in t&&"display"in t&&L(t.outcome)}export function readToolOutcome(t){return isNormalizedToolResult(t)?t.outcome:s(t)&&"execution-denied"===t.type?{kind:TOOL_OUTCOME_KINDS.userRejected,..."string"==typeof t.reason?{reason:t.reason}:{}}:s(t)&&"string"==typeof t.type&&t.type.startsWith("error")||k(t)?{kind:TOOL_OUTCOME_KINDS.toolFailed}:{kind:TOOL_OUTCOME_KINDS.success}}function k(t){return s(t)&&!0===t.isError}export function readIsError(t){return readToolOutcome(t).kind!==TOOL_OUTCOME_KINDS.success}export function readDisplayOutput(t){return isNormalizedToolResult(t)?t.display:s(t)&&"output"in t?t.output:t}export function toolResultToModelOutput(t){return t.model}
@@ -23,16 +23,19 @@ export interface SessionExecDeps {
23
23
  }
24
24
  declare const execCommandInputSchema: z.ZodObject<{
25
25
  command: z.ZodString;
26
+ explanation: z.ZodOptional<z.ZodString>;
26
27
  workdir: z.ZodOptional<z.ZodString>;
27
28
  yield_time_ms: z.ZodOptional<z.ZodNumber>;
28
29
  max_output_tokens: z.ZodOptional<z.ZodNumber>;
29
30
  }, "strip", z.ZodTypeAny, {
30
31
  command: string;
32
+ explanation?: string | undefined;
31
33
  workdir?: string | undefined;
32
34
  max_output_tokens?: number | undefined;
33
35
  yield_time_ms?: number | undefined;
34
36
  }, {
35
37
  command: string;
38
+ explanation?: string | undefined;
36
39
  workdir?: string | undefined;
37
40
  max_output_tokens?: number | undefined;
38
41
  yield_time_ms?: number | undefined;
@@ -1 +1 @@
1
- import{existsSync as e}from"node:fs";import{resolve as o}from"node:path";import{performance as t}from"node:perf_hooks";import{tool as r}from"ai";import{z as n}from"zod";import{CLASSIFICATION_ANNOTATIONS as i,unknownCommandClassifier as s}from"../types/command-classification.js";import{isWithinWorkdirRoot as a}from"../bash/workdir.js";import{withAutoApprovedShellEnv as l}from"../bash/clean-env.js";import{SessionCapError as c}from"../bash/session/session-manager.js";import{pollUntilDeadline as d,throwIfSessionExecAborted as u}from"../bash/session/yield-loop.js";import{SESSION_STATES as m}from"../bash/session/types.js";import{gateToolCall as p}from"./build-tools.js";import{ToolRegistry as _}from"./naming.js";import{TOOL_OUTCOME_KINDS as f,failedToolResult as x,successfulToolResult as g,toolResultToModelOutput as w}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as k,executeCoordinatedTool as h}from"./tool-execution-coordinator.js";import{isUserCancellationSignal as b}from"../types/cancellation.js";export const EXEC_AGENT_NAME="roll";export const EXEC_COMMAND_NAME="exec_command";export const EXEC_POLL_NAME="exec_poll";export const EXEC_LIST_NAME="exec_list";export const EXEC_COMMAND_ID="roll__exec_command";export const EXEC_POLL_ID="roll__exec_poll";export const EXEC_LIST_ID="roll__exec_list";const C=String.fromCharCode(3),y=250,S=3e4,E=5e3,v=3e5,M=8192,I=4096,$=4;function j(e){return"known-safe"===e||"dangerous"===e?e:"unknown"}const N=n.object({command:n.string().min(1).describe("要在后台会话中执行的 shell 命令(单字符串)"),workdir:n.string().min(1).optional().describe("工作目录绝对路径,默认 roll chat 当前目录,不要用 cd"),yield_time_ms:n.number().int().min(y).max(S).optional().describe("本次等待输出的毫秒数(默认 10000,范围 250-30000);未结束会返回 session_id 供 exec_poll 续查"),max_output_tokens:n.number().int().min(256).max(5e4).optional().describe("本次返回输出的 token 预算")}),T=n.object({session_id:n.number().int().describe("exec_command 返回的会话 id"),chars:n.string().default("").describe('留空表示纯轮询进度;"\\u0003"(Ctrl-C) 表示中断该会话;不支持其它交互输入'),yield_time_ms:n.number().int().min(E).max(v).optional().describe("空轮询等待的毫秒数(默认 10000,范围 5000-300000)")}),P=n.object({});function O(e,o,t){return Math.min(Math.max(e,o),t)}function A(e,o,t){const r=e.emitEvent;if(!r)return;let n=0;return(e,i)=>{if(n>=M)return;n+=1;const s=i.length>I?i.slice(0,I):i;r({type:"tool-output-delta",toolCallId:o,agentName:"roll",toolName:t,stream:e,delta:s})}}async function D(e,o,t){if(e.policy)return p(e,"roll","exec_command",o,t);const r=await e.requestApproval({agentName:"roll",toolName:"exec_command",input:o,reason:"shell 命令需确认"});return r.approved?void 0:x(f.userRejected,"已取消执行"+(r.reason?`: ${r.reason}`:""),r.reason?{reason:r.reason}:{})}function L(e){const o=["running"===e.kind?`Session: ${String(e.sessionId)} (running)`:`Exit code: ${String(e.exitCode)}`,`Wall time: ${(e.wallTimeMs/1e3).toFixed(1)} s`];e.omitted>0&&o.push(`(省略中间 ${String(e.omitted)} 字符)`),"exited"===e.kind&&e.terminationCause&&o.push(`Termination: ${e.terminationCause}`),"exited"===e.kind&&e.cleanupError&&o.push(`Cleanup error: ${e.cleanupError}`);const t=e.output.length>0?`\n\n${e.output}`:"",r=o.join("\n")+t;return"exited"===e.kind&&(0!==e.exitCode||e.state===m.cleanupFailed||void 0!==e.terminationCause)?x(f.toolFailed,r,{raw:e}):g(r,{raw:e})}function X(e){return 0===e.length?g("当前没有可恢复的后台会话",{raw:e}):g(JSON.stringify({sessions:e.map(e=>({session_id:e.sessionId,state:e.state,command:e.commandPreview,workdir:e.workdir,wall_time_s:Number((e.wallTimeMs/1e3).toFixed(1)),...void 0!==e.exitCode?{exit_code:e.exitCode}:{},...e.terminationCause?{termination_cause:e.terminationCause}:{},...e.cleanupError?{cleanup_error:e.cleanupError}:{}}))},null,2),{raw:e})}function F(e,o,t){if(void 0===e)return()=>{};const r=()=>{b(e)&&t.get(o.id)===o&&t.interrupt(o.id).catch(()=>{})};return e.addEventListener("abort",r,{once:!0}),e.aborted&&r(),()=>e.removeEventListener("abort",r)}export function buildSessionExecToolset(n,m,p,_,g={}){const b=p.register("roll","exec_command"),M=p.register("roll","exec_poll"),I=p.register("roll","exec_list"),$=g.classifier??s,z=e=>({workdir:o(n.workdir,e.workdir??"."),yieldMs:O(e.yield_time_ms??n.defaultYieldMs,y,S)}),Y=(e,o)=>{const{workdir:t,yieldMs:r}=z(e),s=a(n.workdir,t)?o??$.classify(e.command,t):"unknown";return{workdir:t,yieldMs:r,classification:s,annotations:i[s]}},q={prepare:async(o,t)=>{const r=N.safeParse(o);if(!r.success)return x(f.invalidInput,`参数校验失败: ${r.error.issues.map(e=>e.message).join("; ")}`,{raw:r.error.issues});const n=Y(r.data,j(t));return e(n.workdir)?D(_,{command:r.data.command,workdir:n.workdir,yield_time_ms:n.yieldMs},n.annotations):x(f.invalidInput,`工作目录不存在: ${n.workdir}`)},resources:(e,o)=>{const t=N.safeParse(e),r=t.success?Y(t.data,j(o)).workdir:n.workdir;return[{key:"exec-manager",mode:k.write},{key:`shell:${r}`,mode:k.write}]},captureExecutionState:e=>{const o=N.safeParse(e);return o.success?Y(o.data).classification:"unknown"},revalidateExecution:(e,o)=>{if("known-safe"!==o)return;const t=N.safeParse(e);return t.success&&"known-safe"===Y(t.data).classification?void 0:x(f.toolFailed,"后台 shell 命令的安全条件在准入后发生变化,已在执行前阻止;请重新提交以重新确认")}},G={prepare:e=>{const o=T.safeParse(e);return o.success?m.get(o.data.session_id)?""!==o.data.chars&&o.data.chars!==C?x(f.invalidInput,"pipe 会话不支持交互输入(仅支持空 chars 轮询或 Ctrl-C \\u0003 中断)"):void 0:x(f.invalidInput,`会话 ${String(o.data.session_id)} 不存在或已结束`):x(f.invalidInput,`参数校验失败: ${o.error.issues.map(e=>e.message).join("; ")}`,{raw:o.error.issues})},resources:e=>{const o=T.safeParse(e);return[{key:o.success?`exec-session:${String(o.data.session_id)}`:"exec-session",mode:k.write}]}},H={resources:()=>[{key:"exec-manager",mode:k.read}]};return _.coordinator?.register(b,q),_.coordinator?.register(M,G),_.coordinator?.register(I,H),{[b]:r({description:"在后台会话中执行一条命令,等待一段时间后返回输出。若命令未结束会返回 session_id,用 exec_poll 续查进度、读取退出码。适合运行时间超过单轮预算的长脚本。需确认的命令继承 roll 进程环境;自动批准的 known-safe 命令使用隔离的系统 PATH 与 shell 环境。",inputSchema:N,toModelOutput:({output:e})=>w(e),execute:async(o,r)=>{const{workdir:i,yieldMs:s}=z(o);return h(_.coordinator,q,b,r.toolCallId,o,r.abortSignal,async a=>{if(u(r.abortSignal),!e(i))return x(f.invalidInput,`工作目录不存在: ${i}`);const p=4*(o.max_output_tokens??n.maxOutputTokens),w=A(_,r.toolCallId,"exec_command");let k;try{k=m.spawn({command:o.command,workdir:i,..."known-safe"===a?{env:l(n.env??process.env)}:{},...w?{onDelta:w}:{}}),g.onSessionTouched?.(k.id)}catch(e){const o=e instanceof c?`${e.message};先调用 ${I} 查看会话,再对 cleanup-failed 终态调用 ${M} 领取结果并释放名额,或等待/中断仍在运行的会话后重试`:`无法启动会话: ${String(e)}`;return x(f.toolFailed,o,{raw:e})}const h=F(r.abortSignal,k,m),b=await d(k,t.now()+s,p,{...r.abortSignal?{abortSignal:r.abortSignal}:{},...w?{onDelta:w}:{}}).finally(h);return"exited"===b.kind&&m.delete(k.id),L(b)})}}),[M]:r({description:'轮询或中断一个 exec_command 会话。session_id 为 exec_command 返回的 id;chars 留空表示纯轮询进度,"\\u0003" 表示发送 Ctrl-C 中断。返回最新输出,进程结束时返回退出码。',inputSchema:T,toModelOutput:({output:e})=>w(e),execute:async(e,o)=>h(_.coordinator,G,M,o.toolCallId,e,o.abortSignal,async()=>{u(o.abortSignal);const r=m.get(e.session_id);if(!r)return x(f.invalidInput,`会话 ${String(e.session_id)} 不存在或已结束`);if(""!==e.chars&&e.chars!==C)return x(f.invalidInput,"pipe 会话不支持交互输入(仅支持空 chars 轮询或 Ctrl-C \\u0003 中断)");g.onSessionTouched?.(r.id),e.chars===C&&await m.interrupt(r.id);const i=A(_,o.toolCallId,"exec_poll"),s=O(e.yield_time_ms??Math.max(n.defaultYieldMs,E),E,v),a=4*n.maxOutputTokens,l=F(o.abortSignal,r,m),c=await d(r,t.now()+s,a,{...o.abortSignal?{abortSignal:o.abortSignal}:{},...i?{onDelta:i}:{}}).finally(l);return"exited"===c.kind&&m.delete(e.session_id),L(c)})}),[I]:r({description:"列出当前 roll chat 进程中的有界近期后台命令会话,包括仍在运行的会话和尚未领取最终结果的已结束会话。用于在轮超时或丢失 session_id 后恢复轮询,不是永久历史。",inputSchema:P,toModelOutput:({output:e})=>w(e),execute:async(e,o)=>h(_.coordinator,H,I,o.toolCallId,e,o.abortSignal,()=>Promise.resolve(X(m.list())))})}}
1
+ import{existsSync as e}from"node:fs";import{resolve as o}from"node:path";import{performance as t}from"node:perf_hooks";import{tool as n}from"ai";import{z as r}from"zod";import{CLASSIFICATION_ANNOTATIONS as i,unknownCommandClassifier as s}from"../types/command-classification.js";import{isWithinWorkdirRoot as a}from"../bash/workdir.js";import{withAutoApprovedShellEnv as l}from"../bash/clean-env.js";import{SessionCapError as d}from"../bash/session/session-manager.js";import{pollUntilDeadline as c,throwIfSessionExecAborted as u}from"../bash/session/yield-loop.js";import{SESSION_STATES as m}from"../bash/session/types.js";import{gateToolCall as p}from"./build-tools.js";import{shellCommandExplanationSchema as _}from"./shell-explanation.js";import{ToolRegistry as x}from"./naming.js";import{TOOL_OUTCOME_KINDS as f,failedToolResult as g,successfulToolResult as w,toolResultToModelOutput as k}from"./normalize-result.js";import{TOOL_RESOURCE_ACCESS_MODES as h,executeCoordinatedTool as b}from"./tool-execution-coordinator.js";import{isUserCancellationSignal as C}from"../types/cancellation.js";export const EXEC_AGENT_NAME="roll";export const EXEC_COMMAND_NAME="exec_command";export const EXEC_POLL_NAME="exec_poll";export const EXEC_LIST_NAME="exec_list";export const EXEC_COMMAND_ID="roll__exec_command";export const EXEC_POLL_ID="roll__exec_poll";export const EXEC_LIST_ID="roll__exec_list";const y=String.fromCharCode(3),S=250,v=3e4,E=5e3,M=3e5,I=8192,$=4096,j=4;function N(e){return"known-safe"===e||"dangerous"===e?e:"unknown"}const P=r.object({command:r.string().min(1).describe("要在后台会话中执行的 shell 命令(单字符串)"),explanation:_.optional(),workdir:r.string().min(1).optional().describe("工作目录绝对路径,默认 roll chat 当前目录,不要用 cd"),yield_time_ms:r.number().int().min(S).max(v).optional().describe("本次等待输出的毫秒数(默认 10000,范围 250-30000);未结束会返回 session_id 供 exec_poll 续查"),max_output_tokens:r.number().int().min(256).max(5e4).optional().describe("本次返回输出的 token 预算")}),T=r.object({session_id:r.number().int().describe("exec_command 返回的会话 id"),chars:r.string().default("").describe('留空表示纯轮询进度;"\\u0003"(Ctrl-C) 表示中断该会话;不支持其它交互输入'),yield_time_ms:r.number().int().min(E).max(M).optional().describe("空轮询等待的毫秒数(默认 10000,范围 5000-300000)")}),O=r.object({});function A(e,o,t){return Math.min(Math.max(e,o),t)}function D(e,o,t){const n=e.emitEvent;if(!n)return;let r=0;return(e,i)=>{if(r>=I)return;r+=1;const s=i.length>$?i.slice(0,$):i;n({type:"tool-output-delta",toolCallId:o,agentName:"roll",toolName:t,stream:e,delta:s})}}async function L(e,o,t,n,r){if(e.policy)return p(e,"roll","exec_command",o,n,{includePolicyReason:"dangerous"===t,...void 0!==r?{explanation:r}:{}});const i=await e.requestApproval({agentName:"roll",toolName:"exec_command",input:o,reason:"dangerous"===t?"破坏性操作":void 0,...void 0!==r?{explanation:r}:{}});return i.approved?void 0:g(f.userRejected,"已取消执行"+(i.reason?`: ${i.reason}`:""),i.reason?{reason:i.reason}:{})}function X(e){const o=["running"===e.kind?`Session: ${String(e.sessionId)} (running)`:`Exit code: ${String(e.exitCode)}`,`Wall time: ${(e.wallTimeMs/1e3).toFixed(1)} s`];e.omitted>0&&o.push(`(省略中间 ${String(e.omitted)} 字符)`),"exited"===e.kind&&e.terminationCause&&o.push(`Termination: ${e.terminationCause}`),"exited"===e.kind&&e.cleanupError&&o.push(`Cleanup error: ${e.cleanupError}`);const t=e.output.length>0?`\n\n${e.output}`:"",n=o.join("\n")+t;return"exited"===e.kind&&(0!==e.exitCode||e.state===m.cleanupFailed||void 0!==e.terminationCause)?g(f.toolFailed,n,{raw:e}):w(n,{raw:e})}function F(e){return 0===e.length?w("当前没有可恢复的后台会话",{raw:e}):w(JSON.stringify({sessions:e.map(e=>({session_id:e.sessionId,state:e.state,command:e.commandPreview,workdir:e.workdir,wall_time_s:Number((e.wallTimeMs/1e3).toFixed(1)),...void 0!==e.exitCode?{exit_code:e.exitCode}:{},...e.terminationCause?{termination_cause:e.terminationCause}:{},...e.cleanupError?{cleanup_error:e.cleanupError}:{}}))},null,2),{raw:e})}function z(e,o,t){if(void 0===e)return()=>{};const n=()=>{C(e)&&t.get(o.id)===o&&t.interrupt(o.id).catch(()=>{})};return e.addEventListener("abort",n,{once:!0}),e.aborted&&n(),()=>e.removeEventListener("abort",n)}export function buildSessionExecToolset(r,m,p,_,x={}){const w=p.register("roll","exec_command"),C=p.register("roll","exec_poll"),I=p.register("roll","exec_list"),$=x.classifier??s,j=e=>({workdir:o(r.workdir,e.workdir??"."),yieldMs:A(e.yield_time_ms??r.defaultYieldMs,S,v)}),R=(e,o)=>{const{workdir:t,yieldMs:n}=j(e),s=a(r.workdir,t)?o??$.classify(e.command,t):"unknown";return{workdir:t,yieldMs:n,classification:s,annotations:i[s]}},Y={prepare:async(o,t)=>{const n=P.safeParse(o);if(!n.success)return g(f.invalidInput,`参数校验失败: ${n.error.issues.map(e=>e.message).join("; ")}`,{raw:n.error.issues});const r=R(n.data,N(t));return e(r.workdir)?L(_,{command:n.data.command,workdir:r.workdir,yield_time_ms:r.yieldMs},r.classification,r.annotations,n.data.explanation):g(f.invalidInput,`工作目录不存在: ${r.workdir}`)},resources:(e,o)=>{const t=P.safeParse(e),n=t.success?R(t.data,N(o)).workdir:r.workdir;return[{key:"exec-manager",mode:h.write},{key:`shell:${n}`,mode:h.write}]},captureExecutionState:e=>{const o=P.safeParse(e);return o.success?R(o.data).classification:"unknown"},revalidateExecution:(e,o)=>{if("known-safe"!==o)return;const t=P.safeParse(e);return t.success&&"known-safe"===R(t.data).classification?void 0:g(f.toolFailed,"后台 shell 命令的安全条件在准入后发生变化,已在执行前阻止;请重新提交以重新确认")}},q={prepare:e=>{const o=T.safeParse(e);return o.success?m.get(o.data.session_id)?""!==o.data.chars&&o.data.chars!==y?g(f.invalidInput,"pipe 会话不支持交互输入(仅支持空 chars 轮询或 Ctrl-C \\u0003 中断)"):void 0:g(f.invalidInput,`会话 ${String(o.data.session_id)} 不存在或已结束`):g(f.invalidInput,`参数校验失败: ${o.error.issues.map(e=>e.message).join("; ")}`,{raw:o.error.issues})},resources:e=>{const o=T.safeParse(e);return[{key:o.success?`exec-session:${String(o.data.session_id)}`:"exec-session",mode:h.write}]}},G={resources:()=>[{key:"exec-manager",mode:h.read}]};return _.coordinator?.register(w,Y),_.coordinator?.register(C,q),_.coordinator?.register(I,G),{[w]:n({description:"在后台会话中执行一条命令,等待一段时间后返回输出。若命令未结束会返回 session_id,用 exec_poll 续查进度、读取退出码。适合运行时间超过单轮预算的长脚本。需确认的命令继承 roll 进程环境;自动批准的 known-safe 命令使用隔离的系统 PATH 与 shell 环境。",inputSchema:P,toModelOutput:({output:e})=>k(e),execute:async(o,n)=>{const{workdir:i,yieldMs:s}=j(o);return b(_.coordinator,Y,w,n.toolCallId,o,n.abortSignal,async a=>{if(u(n.abortSignal),!e(i))return g(f.invalidInput,`工作目录不存在: ${i}`);const p=4*(o.max_output_tokens??r.maxOutputTokens),w=D(_,n.toolCallId,"exec_command");let k;try{k=m.spawn({command:o.command,workdir:i,..."known-safe"===a?{env:l(r.env??process.env)}:{},...w?{onDelta:w}:{}}),x.onSessionTouched?.(k.id)}catch(e){const o=e instanceof d?`${e.message};先调用 ${I} 查看会话,再对 cleanup-failed 终态调用 ${C} 领取结果并释放名额,或等待/中断仍在运行的会话后重试`:`无法启动会话: ${String(e)}`;return g(f.toolFailed,o,{raw:e})}const h=z(n.abortSignal,k,m),b=await c(k,t.now()+s,p,{...n.abortSignal?{abortSignal:n.abortSignal}:{},...w?{onDelta:w}:{}}).finally(h);return"exited"===b.kind&&m.delete(k.id),X(b)})}}),[C]:n({description:'轮询或中断一个 exec_command 会话。session_id 为 exec_command 返回的 id;chars 留空表示纯轮询进度,"\\u0003" 表示发送 Ctrl-C 中断。返回最新输出,进程结束时返回退出码。',inputSchema:T,toModelOutput:({output:e})=>k(e),execute:async(e,o)=>b(_.coordinator,q,C,o.toolCallId,e,o.abortSignal,async()=>{u(o.abortSignal);const n=m.get(e.session_id);if(!n)return g(f.invalidInput,`会话 ${String(e.session_id)} 不存在或已结束`);if(""!==e.chars&&e.chars!==y)return g(f.invalidInput,"pipe 会话不支持交互输入(仅支持空 chars 轮询或 Ctrl-C \\u0003 中断)");x.onSessionTouched?.(n.id),e.chars===y&&await m.interrupt(n.id);const i=D(_,o.toolCallId,"exec_poll"),s=A(e.yield_time_ms??Math.max(r.defaultYieldMs,E),E,M),a=4*r.maxOutputTokens,l=z(o.abortSignal,n,m),d=await c(n,t.now()+s,a,{...o.abortSignal?{abortSignal:o.abortSignal}:{},...i?{onDelta:i}:{}}).finally(l);return"exited"===d.kind&&m.delete(e.session_id),X(d)})}),[I]:n({description:"列出当前 roll chat 进程中的有界近期后台命令会话,包括仍在运行的会话和尚未领取最终结果的已结束会话。用于在轮超时或丢失 session_id 后恢复轮询,不是永久历史。",inputSchema:O,toModelOutput:({output:e})=>k(e),execute:async(e,o)=>b(_.coordinator,G,I,o.toolCallId,e,o.abortSignal,()=>Promise.resolve(F(m.list())))})}}
@@ -0,0 +1,2 @@
1
+ import { z } from "zod";
2
+ export declare const shellCommandExplanationSchema: z.ZodString;
@@ -0,0 +1 @@
1
+ import{APPROVAL_EXPLANATION_MAX_CHARS as o}from"@roll-agent/protocol";import{z as m}from"zod";export const shellCommandExplanationSchema=m.string().trim().min(1).max(o).describe("面向非技术用户,使用用户当前语言,用一句话说明命令会做什么以及为何有助于当前任务。建议 40-60 字符,最多 100 字符;不要复述命令、声称命令安全或包含敏感值");
@@ -1 +1 @@
1
- import{createHash as e,randomUUID as t}from"node:crypto";import{TOOL_OUTCOME_KINDS as n}from"./normalize-result.js";export const TOOL_EXECUTION_RECORD_VERSION=1;export const TOOL_EXECUTION_VALUE_ENVELOPE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_LIMITS={inputBytes:32768,rawBytes:65536,modelBytes:65536,displayBytes:32768,outcomeBytes:8192,identifierBytes:4096,recordBytes:262144};export const TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS={sizeLimit:"size_limit",recordLimit:"record_limit"};export const TOOL_EXECUTION_VALUE_ENCODINGS={json:"json",diagnostic:"diagnostic",redacted:"redacted"};export const TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS={error:"error",undefined:"undefined",bigint:"bigint",cycle:"cycle",nonFiniteNumber:"non_finite_number",unsupported:"unsupported",unreadable:"unreadable"};const o=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu,r=Object.freeze({version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.redacted}),i="[redacted]",a=/(?:密码|口令|密钥|秘钥|令牌|凭证|授权(?:信息)?)$/u,s=new Set(["accesskey","accesstoken","apikey","authkey","authsecret","authtoken","bearertoken","clientkey","clientsecret","connectionstring","privatekey","refreshtoken","secretkey","sessiontoken"]),c=/(?<![\p{L}\p{N}_.-])(?:(['"`])([\p{L}_][\p{L}\p{N}_.-]*)\1|([\p{L}_][\p{L}\p{N}_.-]*))(\s*[:=:=]\s*)/gu,u=/data:[^,\s]+;base64,[a-z0-9+/_=-]+(?:(?:[ \t]*\r?\n[ \t]*|[ \t]*\\(?:r\\n|n)[ \t]*)[a-z0-9+/_=-]+)*/giu,d=/-----BEGIN(?: [A-Z0-9]+)* PRIVATE KEY-----[\s\S]*?(?:-----END(?: [A-Z0-9]+)* PRIVATE KEY-----|$)/giu,l=/^[a-f0-9]{64}$/u;function p(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function E(e,t=0){return!(t>64)&&(!!(null===e||"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e))||(Array.isArray(e)?e.every(e=>E(e,t+1)):p(e)&&Object.values(e).every(e=>E(e,t+1))))}function f(e){return p(e)&&Object.values(TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS).includes(e.kind)&&"string"==typeof e.path&&"string"==typeof e.message&&(void 0===e.detail||"string"==typeof e.detail)&&(void 0===e.referencePath||"string"==typeof e.referencePath)}function O(e){return!(!p(e)||1!==e.version||!E(e.value))&&(e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json||e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic&&Array.isArray(e.diagnostics)&&e.diagnostics.every(f))}function y(e){return!!p(e)&&(e.kind===n.success||Object.values(n).includes(e.kind)&&(void 0===e.reason||"string"==typeof e.reason))}function I(e){return!!p(e)&&("text"===e.type?"string"==typeof e.text:"file"===e.type&&p(e.data)&&"data"===e.data.type&&"string"==typeof e.data.data&&"string"==typeof e.mediaType&&(void 0===e.filename||"string"==typeof e.filename))}function N(e){return!(!p(e)||"string"!=typeof e.type)&&("text"===e.type||"error-text"===e.type?"string"==typeof e.value:"json"===e.type||"error-json"===e.type?E(e.value):"execution-denied"===e.type?void 0===e.reason||"string"==typeof e.reason:"content"===e.type&&Array.isArray(e.value)&&e.value.every(I))}function _(e,t){return`${e}[${JSON.stringify(t)}]`}function m(e,t){return`${e}[${String(t)}]`}function T(e,t){return`${e}[${String(t)}]`}function g(e){try{return String(e)}catch{return"[value could not be stringified]"}}function S(e){return{$rollDiagnostic:{kind:e.kind,path:e.path,message:e.message,...void 0!==e.detail?{detail:e.detail}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}}function v(e,t){return e.diagnostics.push(t),S(t)}function L(e,t){let n;try{n=e.stack}catch{n=void 0}let o="Error",r="Error value could not be inspected";try{o=e.name,r=e.message}catch{}return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.error,path:t,message:`${o}: ${r}`,...void 0!==n?{detail:n}:{}}}function C(e,t,n){return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unsupported,path:e,message:t,...void 0!==n?{detail:n}:{}}}function b(e,t,n){Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0})}function x(e,t,n){const o=[];for(let r=0;r<e.length;r+=1){const i=m(t,r);if(!Object.prototype.hasOwnProperty.call(e,r)){o.push(v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:i,message:"Sparse array slots are not JSON values"}));continue}let a;try{a=e[r]}catch(e){o.push(v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:i,message:"Array item could not be read",detail:g(e)}));continue}o.push(U(a,i,n))}for(const o of Reflect.ownKeys(e)){if("length"===o||"string"==typeof o&&/^(0|[1-9][0-9]*)$/u.test(o))continue;v(n,C("symbol"==typeof o?T(t,o):_(t,o),"Non-index array properties are not representable in JSON"))}return o}function h(e,t,n){const o={};for(const r of Reflect.ownKeys(e)){const i="symbol"==typeof r?T(t,r):_(t,r);if("symbol"==typeof r){v(n,C(i,"Symbol-keyed properties are not representable in JSON"));continue}const a=Object.getOwnPropertyDescriptor(e,r);void 0!==a?a.enumerable?b(o,r,"value"in a?U(a.value,i,n):v(n,C(i,"Accessor properties are not read during persistence"))):v(n,C(i,"Non-enumerable properties are not representable in JSON")):v(n,C(i,"Property descriptor disappeared while encoding"))}return o}function A(e,t,n){const o=n.ancestors.get(e);if(void 0!==o)return v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.cycle,path:t,message:"Cyclic references are not representable in JSON",referencePath:o});n.ancestors.set(e,t);try{if(e instanceof Error)return v(n,L(e,t));if(Array.isArray(e))return x(e,t,n);const o=Object.getPrototypeOf(e);return o!==Object.prototype&&null!==o?v(n,C(t,"Only plain objects and arrays are losslessly representable in JSON",g(e))):h(e,t,n)}catch(e){return v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:t,message:"Object could not be inspected for persistence",detail:g(e)})}finally{n.ancestors.delete(e)}}function U(e,t,n){return null===e||"string"==typeof e||"boolean"==typeof e?e:"number"==typeof e?Number.isFinite(e)?e:v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.nonFiniteNumber,path:t,message:"Non-finite numbers are not JSON values",detail:g(e)}):void 0===e?v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:t,message:"undefined is not a JSON value"}):"bigint"==typeof e?v(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.bigint,path:t,message:"BigInt is not a JSON value",detail:e.toString()}):"symbol"==typeof e?v(n,C(t,"Symbols are not JSON values",g(e))):"function"==typeof e?v(n,C(t,"Functions are not JSON values",e.name||"anonymous")):A(e,t,n)}function R(e){return encodeToolExecutionValue(e).value}function k(e){return"text"===e.type?{type:"text",text:e.text}:{type:"file",data:{type:"data",data:e.data.data},mediaType:e.mediaType,...void 0!==e.filename?{filename:e.filename}:{}}}function D(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:e.value}:"json"===e.type||"error-json"===e.type?{type:e.type,value:R(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:{type:"content",value:e.value.map(k)}}function X(e){return"success"===e.kind?{kind:"success"}:{kind:e.kind,...void 0!==e.reason?{reason:e.reason}:{}}}function V(e){return e.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(e=>e.length>0)}export function isSensitiveFieldName(e){if("_meta"===e)return!0;const t=e.trim();if(a.test(t))return!0;const n=t.toLowerCase().replace(/[^a-z0-9]+/gu,"");if(s.has(n))return!0;const o=V(e);if(o.some(e=>["authorization","cookie","credential","credentials","passphrase","passwd","password","secret"].includes(e)))return!0;if(o.includes("token"))return!o.some(e=>["budget","count","limit","maximum","minimum","total","usage"].includes(e));const r=o.some(e=>["dsn","uri","url"].includes(e)),i=o.some(e=>["connection","database","datasource","db","jdbc","mongo","mongodb","mysql","postgres","postgresql","redis"].includes(e));return!!(r&&i||o.includes("connection")&&o.includes("string"))||o.includes("key")&&o.some(e=>["access","api","auth","client","private","secret"].includes(e))}function P(e){if(void 0===e)return!1;const t=V(e);return 1===t.length&&["audio","data","image"].includes(t[0]??"")||t.some(e=>["base64","blob","bytes"].includes(e))||t.includes("data")&&t.some(e=>["audio","binary","image"].includes(e))}function $(e){return`[binary content omitted: ${String(e.length)} chars]`}function j(e,t,n){let o=!1;for(let r=t+1;r<e.length;r+=1){const t=e[r];if(o)o=!1;else if("\\"!==t){if(t===n)return r+1}else o=!0}return e.length}function B(e,t){const n=e[t];if('"'===n||"'"===n||"`"===n)return j(e,t,n);if("|"===n||">"===n)return e.length;const o=/[\r\n]/u.exec(e.slice(t));return null===o?e.length:t+o.index}function w(e,t){let n=0,o="";for(const r of e.matchAll(c)){if(r.index<n)continue;const i=r[2]??r[3];if(void 0===i||!isSensitiveFieldName(i))continue;const a=r.index+r[0].length,s=B(e,a);o+=`${e.slice(n,a)}${t}`,n=Math.max(a,s)}return`${o}${e.slice(n)}`}export function redactSecretText(e,t=i){return w(e,t).replace(d,t).replace(/\bbearer\s+[a-z0-9._~+/-]+=*/giu,`Bearer ${t}`).replace(/\b(?:github_pat_[a-z0-9_]{20,}|gh[pousr]_[a-z0-9]{20,})\b/giu,t).replace(/\bxox[a-z]-[a-z0-9-]{12,}\b/giu,t).replace(/\bAKIA[0-9A-Z]{16}\b/gu,t).replace(/\b(?:pk|rk|sk)-[a-z0-9_-]{8,}\b/giu,t).replace(/\beyJ[a-z0-9_-]{8,}\.[a-z0-9_-]+\.[a-z0-9_-]+\b/giu,t)}function z(e,t){return P(t)||/^data:[^,\s]+;base64,/iu.test(e)?$(e):redactSecretText(e).replace(u,e=>$(e))}function G(e,t,n=0){if(n>64)return"[nested value omitted]";if("string"==typeof e)return z(e,t);if(null===e||"boolean"==typeof e||"number"==typeof e)return e;if(Array.isArray(e))return e.map(e=>G(e,void 0,n+1));const o={};for(const[t,r]of Object.entries(e))o[t]=void 0===r||isSensitiveFieldName(t)?i:G(r,t,n+1);return o}function M(e){return{kind:e.kind,path:e.path,message:z(e.message),...void 0!==e.detail?{detail:z(e.detail)}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}function K(e){return e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json?{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:G(e.value)}:{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:G(e.value),diagnostics:e.diagnostics.map(M)}}function J(e){return"text"===e.type?{type:"text",text:z(e.text)}:{type:"file",data:{type:"data",data:$(e.data.data)},mediaType:z(e.mediaType),...void 0!==e.filename?{filename:z(e.filename)}:{}}}function F(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:z(e.value)}:"json"===e.type||"error-json"===e.type?{type:e.type,value:G(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:z(e.reason)}:{}}:{type:"content",value:e.value.map(J)}}function Z(e){return e.kind===n.success?{kind:n.success}:{kind:e.kind,...void 0!==e.reason?{reason:z(e.reason)}:{}}}function q(e){return JSON.stringify(e)}function Y(e){return Buffer.byteLength(e,"utf8")}function W(t){return e("sha256").update(t,"utf8").digest("hex")}function H(e,t){return{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:{$rollEvidence:{omitted:!0,reason:t,redactedSha256:e}}}}function Q(e,t,n,o){return{originalByteLength:Y(e),storedByteLength:Y(n),redactedSha256:W(t),redactionApplied:!0,truncated:void 0!==o,...void 0!==o?{omissionReason:o}:{}}}function ee(e,t,n){const o=q(e),r=K(e),i=q(r),a=n??(Y(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0);if(void 0===a)return{value:r,metadata:Q(o,i,i)};const s=H(W(i),a);return{value:s,metadata:Q(o,i,q(s),a)}}function te(e,t,n){const o=z(e);if(Y(o)<=t)return{value:o,truncated:!1};const r=`…[${n} omitted; sha256:${W(o)}]`,i=Y(r);if(i>=t)return{value:r.slice(0,t),truncated:!0};let a=o;for(;a.length>0&&Y(a)+i>t;)a=a.slice(0,Math.floor(.9*a.length));return{value:`${a}${r}`,truncated:!0}}function ne(e,t,n){const o=q(e),r=F(e),i=q(r),a=n??(Y(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),s=void 0===a?r:{type:"text",value:`[durable model evidence omitted; sha256:${W(i)}]`};return{value:s,metadata:Q(o,i,q(s),a)}}function oe(e,t,o){const r=q(e),i=Z(e),a=q(i),s=o??(Y(a)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),c=void 0===s||i.kind===n.success?i:{kind:i.kind,reason:`[durable outcome detail omitted; sha256:${W(a)}]`};return{value:c,metadata:Q(r,a,q(c),s)}}function re(e){return p(e)&&Number.isInteger(e.originalByteLength)&&"number"==typeof e.originalByteLength&&e.originalByteLength>=0&&Number.isInteger(e.storedByteLength)&&"number"==typeof e.storedByteLength&&e.storedByteLength>=0&&"string"==typeof e.redactedSha256&&l.test(e.redactedSha256)&&!0===e.redactionApplied&&"boolean"==typeof e.truncated&&(void 0===e.omissionReason||Object.values(TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS).includes(e.omissionReason))}function ie(e){return p(e)&&1===e.version&&p(e.fields)&&re(e.fields.input)&&re(e.fields.raw)&&re(e.fields.model)&&re(e.fields.display)&&re(e.fields.outcome)}export function isPersistedToolExecutionRecord(e){return isToolExecutionRecord(e)&&p(e)&&ie(e.persistence)}function ae(e,t){const n=ee(e.input,TOOL_EXECUTION_PERSISTENCE_LIMITS.inputBytes,t),o=ee(e.raw,TOOL_EXECUTION_PERSISTENCE_LIMITS.rawBytes,t),r=ne(e.model,TOOL_EXECUTION_PERSISTENCE_LIMITS.modelBytes,t),i=ee(e.display,TOOL_EXECUTION_PERSISTENCE_LIMITS.displayBytes,t),a=oe(e.outcome,TOOL_EXECUTION_PERSISTENCE_LIMITS.outcomeBytes,t);return{version:e.version,id:e.id,toolCallId:te(e.toolCallId,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolCallId").value,agentName:te(e.agentName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"agentName").value,toolName:te(e.toolName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolName").value,createdAt:te(e.createdAt,256,"createdAt").value,input:n.value,raw:o.value,model:r.value,display:i.value,outcome:a.value,persistence:{version:1,fields:{input:n.metadata,raw:o.metadata,model:r.metadata,display:i.metadata,outcome:a.metadata}}}}export function prepareToolExecutionRecordForPersistence(e){const t=ae(e);return Y(q(t))<=TOOL_EXECUTION_PERSISTENCE_LIMITS.recordBytes?t:ae(e,TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.recordLimit)}export function parsePersistedToolExecutionRecord(e){return isPersistedToolExecutionRecord(e)?e:prepareToolExecutionRecordForPersistence(parseToolExecutionRecord(e))}export function isToolExecutionRecordId(e){return"string"==typeof e&&o.test(e)}export function parseToolExecutionRecordId(e){if(!isToolExecutionRecordId(e))throw new Error(`Invalid ToolExecutionRecord UUID: ${e}`);return e}export function createToolExecutionRecordId(){return parseToolExecutionRecordId(t())}export function isToolExecutionRecord(e){return p(e)&&1===e.version&&isToolExecutionRecordId(e.id)&&"string"==typeof e.toolCallId&&"string"==typeof e.agentName&&"string"==typeof e.toolName&&"string"==typeof e.createdAt&&O(e.input)&&O(e.raw)&&N(e.model)&&O(e.display)&&y(e.outcome)}export function parseToolExecutionRecord(e){if(!isToolExecutionRecord(e))throw new Error("Invalid persisted ToolExecutionRecord");return e}export function encodeToolExecutionValue(e){const t={ancestors:new WeakMap,diagnostics:[]},n=U(e,"$",t);return 0===t.diagnostics.length?{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:n}:{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:n,diagnostics:t.diagnostics}}export function createToolExecutionRecord(e){return{version:1,id:void 0!==e.id?parseToolExecutionRecordId(e.id):createToolExecutionRecordId(),toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt??(new Date).toISOString(),input:encodeToolExecutionValue(e.input),raw:encodeToolExecutionValue(e.result.raw),model:D(e.result.model),display:encodeToolExecutionValue(e.result.display),outcome:X(e.result.outcome)}}export function toRedactedToolExecutionRecordSummary(e){return{version:e.version,id:e.id,toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt,input:r,raw:r,model:F(e.model),display:K(e.display),outcome:Z(e.outcome),...isPersistedToolExecutionRecord(e)?{persistence:e.persistence}:{}}}
1
+ import{createHash as e,randomUUID as t}from"node:crypto";import{TOOL_OUTCOME_KINDS as n,isToolCancellationExecutionState as o}from"./normalize-result.js";export const TOOL_EXECUTION_RECORD_VERSION=1;export const TOOL_EXECUTION_VALUE_ENVELOPE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_VERSION=1;export const TOOL_EXECUTION_PERSISTENCE_LIMITS={inputBytes:32768,rawBytes:65536,modelBytes:65536,displayBytes:32768,outcomeBytes:8192,identifierBytes:4096,recordBytes:262144};export const TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS={sizeLimit:"size_limit",recordLimit:"record_limit"};export const TOOL_EXECUTION_VALUE_ENCODINGS={json:"json",diagnostic:"diagnostic",redacted:"redacted"};export const TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS={error:"error",undefined:"undefined",bigint:"bigint",cycle:"cycle",nonFiniteNumber:"non_finite_number",unsupported:"unsupported",unreadable:"unreadable"};const r=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu,i=Object.freeze({version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.redacted}),a="[redacted]",s=/(?:密码|口令|密钥|秘钥|令牌|凭证|授权(?:信息)?)$/u,c=new Set(["accesskey","accesstoken","apikey","authkey","authsecret","authtoken","bearertoken","clientkey","clientsecret","connectionstring","privatekey","refreshtoken","secretkey","sessiontoken"]),u=/(?<![\p{L}\p{N}_.-])(?:(['"`])([\p{L}_][\p{L}\p{N}_.-]*)\1|([\p{L}_][\p{L}\p{N}_.-]*))(\s*[:=:=]\s*)/gu,d=/data:[^,\s]+;base64,[a-z0-9+/_=-]+(?:(?:[ \t]*\r?\n[ \t]*|[ \t]*\\(?:r\\n|n)[ \t]*)[a-z0-9+/_=-]+)*/giu,l=/-----BEGIN(?: [A-Z0-9]+)* PRIVATE KEY-----[\s\S]*?(?:-----END(?: [A-Z0-9]+)* PRIVATE KEY-----|$)/giu,p=/^[a-f0-9]{64}$/u;function E(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function f(e,t=0){return!(t>64)&&(!!(null===e||"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e))||(Array.isArray(e)?e.every(e=>f(e,t+1)):E(e)&&Object.values(e).every(e=>f(e,t+1))))}function O(e){return E(e)&&Object.values(TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS).includes(e.kind)&&"string"==typeof e.path&&"string"==typeof e.message&&(void 0===e.detail||"string"==typeof e.detail)&&(void 0===e.referencePath||"string"==typeof e.referencePath)}function y(e){return!(!E(e)||1!==e.version||!f(e.value))&&(e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json||e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic&&Array.isArray(e.diagnostics)&&e.diagnostics.every(O))}function I(e){return!!E(e)&&(e.kind===n.success?void 0===e.reason&&void 0===e.executionState:!(!Object.values(n).includes(e.kind)||void 0!==e.reason&&"string"!=typeof e.reason)&&(e.kind!==n.cancelled?void 0===e.executionState:void 0===e.executionState||o(e.executionState)))}function N(e){return!!E(e)&&("text"===e.type?"string"==typeof e.text:"file"===e.type&&E(e.data)&&"data"===e.data.type&&"string"==typeof e.data.data&&"string"==typeof e.mediaType&&(void 0===e.filename||"string"==typeof e.filename))}function _(e){return!(!E(e)||"string"!=typeof e.type)&&("text"===e.type||"error-text"===e.type?"string"==typeof e.value:"json"===e.type||"error-json"===e.type?f(e.value):"execution-denied"===e.type?void 0===e.reason||"string"==typeof e.reason:"content"===e.type&&Array.isArray(e.value)&&e.value.every(N))}function m(e,t){return`${e}[${JSON.stringify(t)}]`}function T(e,t){return`${e}[${String(t)}]`}function g(e,t){return`${e}[${String(t)}]`}function S(e){try{return String(e)}catch{return"[value could not be stringified]"}}function v(e){return{$rollDiagnostic:{kind:e.kind,path:e.path,message:e.message,...void 0!==e.detail?{detail:e.detail}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}}function x(e,t){return e.diagnostics.push(t),v(t)}function L(e,t){let n;try{n=e.stack}catch{n=void 0}let o="Error",r="Error value could not be inspected";try{o=e.name,r=e.message}catch{}return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.error,path:t,message:`${o}: ${r}`,...void 0!==n?{detail:n}:{}}}function C(e,t,n){return{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unsupported,path:e,message:t,...void 0!==n?{detail:n}:{}}}function b(e,t,n){Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0})}function h(e,t,n){const o=[];for(let r=0;r<e.length;r+=1){const i=T(t,r);if(!Object.prototype.hasOwnProperty.call(e,r)){o.push(x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:i,message:"Sparse array slots are not JSON values"}));continue}let a;try{a=e[r]}catch(e){o.push(x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:i,message:"Array item could not be read",detail:S(e)}));continue}o.push(R(a,i,n))}for(const o of Reflect.ownKeys(e)){if("length"===o||"string"==typeof o&&/^(0|[1-9][0-9]*)$/u.test(o))continue;x(n,C("symbol"==typeof o?g(t,o):m(t,o),"Non-index array properties are not representable in JSON"))}return o}function A(e,t,n){const o={};for(const r of Reflect.ownKeys(e)){const i="symbol"==typeof r?g(t,r):m(t,r);if("symbol"==typeof r){x(n,C(i,"Symbol-keyed properties are not representable in JSON"));continue}const a=Object.getOwnPropertyDescriptor(e,r);void 0!==a?a.enumerable?b(o,r,"value"in a?R(a.value,i,n):x(n,C(i,"Accessor properties are not read during persistence"))):x(n,C(i,"Non-enumerable properties are not representable in JSON")):x(n,C(i,"Property descriptor disappeared while encoding"))}return o}function U(e,t,n){const o=n.ancestors.get(e);if(void 0!==o)return x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.cycle,path:t,message:"Cyclic references are not representable in JSON",referencePath:o});n.ancestors.set(e,t);try{if(e instanceof Error)return x(n,L(e,t));if(Array.isArray(e))return h(e,t,n);const o=Object.getPrototypeOf(e);return o!==Object.prototype&&null!==o?x(n,C(t,"Only plain objects and arrays are losslessly representable in JSON",S(e))):A(e,t,n)}catch(e){return x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.unreadable,path:t,message:"Object could not be inspected for persistence",detail:S(e)})}finally{n.ancestors.delete(e)}}function R(e,t,n){return null===e||"string"==typeof e||"boolean"==typeof e?e:"number"==typeof e?Number.isFinite(e)?e:x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.nonFiniteNumber,path:t,message:"Non-finite numbers are not JSON values",detail:S(e)}):void 0===e?x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.undefined,path:t,message:"undefined is not a JSON value"}):"bigint"==typeof e?x(n,{kind:TOOL_EXECUTION_VALUE_DIAGNOSTIC_KINDS.bigint,path:t,message:"BigInt is not a JSON value",detail:e.toString()}):"symbol"==typeof e?x(n,C(t,"Symbols are not JSON values",S(e))):"function"==typeof e?x(n,C(t,"Functions are not JSON values",e.name||"anonymous")):U(e,t,n)}function k(e){return encodeToolExecutionValue(e).value}function D(e){return"text"===e.type?{type:"text",text:e.text}:{type:"file",data:{type:"data",data:e.data.data},mediaType:e.mediaType,...void 0!==e.filename?{filename:e.filename}:{}}}function X(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:e.value}:"json"===e.type||"error-json"===e.type?{type:e.type,value:k(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:e.reason}:{}}:{type:"content",value:e.value.map(D)}}function V(e){return"success"===e.kind?{kind:"success"}:{kind:e.kind,...void 0!==e.reason?{reason:e.reason}:{},...e.kind===n.cancelled&&void 0!==e.executionState?{executionState:e.executionState}:{}}}function P(e){return e.replace(/([a-z0-9])([A-Z])/gu,"$1_$2").toLowerCase().split(/[^a-z0-9]+/u).filter(e=>e.length>0)}export function isSensitiveFieldName(e){if("_meta"===e)return!0;const t=e.trim();if(s.test(t))return!0;const n=t.toLowerCase().replace(/[^a-z0-9]+/gu,"");if(c.has(n))return!0;const o=P(e);if(o.some(e=>["authorization","cookie","credential","credentials","passphrase","passwd","password","secret"].includes(e)))return!0;if(o.includes("token"))return!o.some(e=>["budget","count","limit","maximum","minimum","total","usage"].includes(e));const r=o.some(e=>["dsn","uri","url"].includes(e)),i=o.some(e=>["connection","database","datasource","db","jdbc","mongo","mongodb","mysql","postgres","postgresql","redis"].includes(e));return!!(r&&i||o.includes("connection")&&o.includes("string"))||o.includes("key")&&o.some(e=>["access","api","auth","client","private","secret"].includes(e))}function $(e){if(void 0===e)return!1;const t=P(e);return 1===t.length&&["audio","data","image"].includes(t[0]??"")||t.some(e=>["base64","blob","bytes"].includes(e))||t.includes("data")&&t.some(e=>["audio","binary","image"].includes(e))}function j(e){return`[binary content omitted: ${String(e.length)} chars]`}function B(e,t,n){let o=!1;for(let r=t+1;r<e.length;r+=1){const t=e[r];if(o)o=!1;else if("\\"!==t){if(t===n)return r+1}else o=!0}return e.length}function w(e,t){const n=e[t];if('"'===n||"'"===n||"`"===n)return B(e,t,n);if("|"===n||">"===n)return e.length;const o=/[\r\n]/u.exec(e.slice(t));return null===o?e.length:t+o.index}function z(e,t){let n=0,o="";for(const r of e.matchAll(u)){if(r.index<n)continue;const i=r[2]??r[3];if(void 0===i||!isSensitiveFieldName(i))continue;const a=r.index+r[0].length,s=w(e,a);o+=`${e.slice(n,a)}${t}`,n=Math.max(a,s)}return`${o}${e.slice(n)}`}export function redactSecretText(e,t=a){return z(e,t).replace(l,t).replace(/\bbearer\s+[a-z0-9._~+/-]+=*/giu,`Bearer ${t}`).replace(/\b(?:github_pat_[a-z0-9_]{20,}|gh[pousr]_[a-z0-9]{20,})\b/giu,t).replace(/\bxox[a-z]-[a-z0-9-]{12,}\b/giu,t).replace(/\bAKIA[0-9A-Z]{16}\b/gu,t).replace(/\b(?:pk|rk|sk)-[a-z0-9_-]{8,}\b/giu,t).replace(/\beyJ[a-z0-9_-]{8,}\.[a-z0-9_-]+\.[a-z0-9_-]+\b/giu,t)}function G(e,t){return $(t)||/^data:[^,\s]+;base64,/iu.test(e)?j(e):redactSecretText(e).replace(d,e=>j(e))}function M(e,t,n=0){if(n>64)return"[nested value omitted]";if("string"==typeof e)return G(e,t);if(null===e||"boolean"==typeof e||"number"==typeof e)return e;if(Array.isArray(e))return e.map(e=>M(e,void 0,n+1));const o={};for(const[t,r]of Object.entries(e))o[t]=void 0===r||isSensitiveFieldName(t)?a:M(r,t,n+1);return o}function K(e){return{kind:e.kind,path:e.path,message:G(e.message),...void 0!==e.detail?{detail:G(e.detail)}:{},...void 0!==e.referencePath?{referencePath:e.referencePath}:{}}}function J(e){return e.encoding===TOOL_EXECUTION_VALUE_ENCODINGS.json?{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:M(e.value)}:{version:e.version,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:M(e.value),diagnostics:e.diagnostics.map(K)}}function F(e){return"text"===e.type?{type:"text",text:G(e.text)}:{type:"file",data:{type:"data",data:j(e.data.data)},mediaType:G(e.mediaType),...void 0!==e.filename?{filename:G(e.filename)}:{}}}function Z(e){return"text"===e.type||"error-text"===e.type?{type:e.type,value:G(e.value)}:"json"===e.type||"error-json"===e.type?{type:e.type,value:M(e.value)}:"execution-denied"===e.type?{type:"execution-denied",...void 0!==e.reason?{reason:G(e.reason)}:{}}:{type:"content",value:e.value.map(F)}}function q(e){return e.kind===n.success?{kind:n.success}:{kind:e.kind,...void 0!==e.reason?{reason:G(e.reason)}:{},...e.kind===n.cancelled&&void 0!==e.executionState?{executionState:e.executionState}:{}}}function Y(e){return JSON.stringify(e)}function W(e){return Buffer.byteLength(e,"utf8")}function H(t){return e("sha256").update(t,"utf8").digest("hex")}function Q(e,t){return{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:{$rollEvidence:{omitted:!0,reason:t,redactedSha256:e}}}}function ee(e,t,n,o){return{originalByteLength:W(e),storedByteLength:W(n),redactedSha256:H(t),redactionApplied:!0,truncated:void 0!==o,...void 0!==o?{omissionReason:o}:{}}}function te(e,t,n){const o=Y(e),r=J(e),i=Y(r),a=n??(W(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0);if(void 0===a)return{value:r,metadata:ee(o,i,i)};const s=Q(H(i),a);return{value:s,metadata:ee(o,i,Y(s),a)}}function ne(e,t,n){const o=G(e);if(W(o)<=t)return{value:o,truncated:!1};const r=`…[${n} omitted; sha256:${H(o)}]`,i=W(r);if(i>=t)return{value:r.slice(0,t),truncated:!0};let a=o;for(;a.length>0&&W(a)+i>t;)a=a.slice(0,Math.floor(.9*a.length));return{value:`${a}${r}`,truncated:!0}}function oe(e,t,n){const o=Y(e),r=Z(e),i=Y(r),a=n??(W(i)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),s=void 0===a?r:{type:"text",value:`[durable model evidence omitted; sha256:${H(i)}]`};return{value:s,metadata:ee(o,i,Y(s),a)}}function re(e,t,o){const r=Y(e),i=q(e),a=Y(i),s=o??(W(a)>t?TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.sizeLimit:void 0),c=void 0===s||i.kind===n.success?i:{kind:i.kind,reason:`[durable outcome detail omitted; sha256:${H(a)}]`,...i.kind===n.cancelled&&void 0!==i.executionState?{executionState:i.executionState}:{}};return{value:c,metadata:ee(r,a,Y(c),s)}}function ie(e){return E(e)&&Number.isInteger(e.originalByteLength)&&"number"==typeof e.originalByteLength&&e.originalByteLength>=0&&Number.isInteger(e.storedByteLength)&&"number"==typeof e.storedByteLength&&e.storedByteLength>=0&&"string"==typeof e.redactedSha256&&p.test(e.redactedSha256)&&!0===e.redactionApplied&&"boolean"==typeof e.truncated&&(void 0===e.omissionReason||Object.values(TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS).includes(e.omissionReason))}function ae(e){return E(e)&&1===e.version&&E(e.fields)&&ie(e.fields.input)&&ie(e.fields.raw)&&ie(e.fields.model)&&ie(e.fields.display)&&ie(e.fields.outcome)}export function isPersistedToolExecutionRecord(e){return isToolExecutionRecord(e)&&E(e)&&ae(e.persistence)}function se(e,t){const n=te(e.input,TOOL_EXECUTION_PERSISTENCE_LIMITS.inputBytes,t),o=te(e.raw,TOOL_EXECUTION_PERSISTENCE_LIMITS.rawBytes,t),r=oe(e.model,TOOL_EXECUTION_PERSISTENCE_LIMITS.modelBytes,t),i=te(e.display,TOOL_EXECUTION_PERSISTENCE_LIMITS.displayBytes,t),a=re(e.outcome,TOOL_EXECUTION_PERSISTENCE_LIMITS.outcomeBytes,t);return{version:e.version,id:e.id,toolCallId:ne(e.toolCallId,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolCallId").value,agentName:ne(e.agentName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"agentName").value,toolName:ne(e.toolName,TOOL_EXECUTION_PERSISTENCE_LIMITS.identifierBytes,"toolName").value,createdAt:ne(e.createdAt,256,"createdAt").value,input:n.value,raw:o.value,model:r.value,display:i.value,outcome:a.value,persistence:{version:1,fields:{input:n.metadata,raw:o.metadata,model:r.metadata,display:i.metadata,outcome:a.metadata}}}}export function prepareToolExecutionRecordForPersistence(e){const t=se(e);return W(Y(t))<=TOOL_EXECUTION_PERSISTENCE_LIMITS.recordBytes?t:se(e,TOOL_EXECUTION_EVIDENCE_OMISSION_REASONS.recordLimit)}export function parsePersistedToolExecutionRecord(e){return isPersistedToolExecutionRecord(e)?e:prepareToolExecutionRecordForPersistence(parseToolExecutionRecord(e))}export function isToolExecutionRecordId(e){return"string"==typeof e&&r.test(e)}export function parseToolExecutionRecordId(e){if(!isToolExecutionRecordId(e))throw new Error(`Invalid ToolExecutionRecord UUID: ${e}`);return e}export function createToolExecutionRecordId(){return parseToolExecutionRecordId(t())}export function isToolExecutionRecord(e){return E(e)&&1===e.version&&isToolExecutionRecordId(e.id)&&"string"==typeof e.toolCallId&&"string"==typeof e.agentName&&"string"==typeof e.toolName&&"string"==typeof e.createdAt&&y(e.input)&&y(e.raw)&&_(e.model)&&y(e.display)&&I(e.outcome)}export function parseToolExecutionRecord(e){if(!isToolExecutionRecord(e))throw new Error("Invalid persisted ToolExecutionRecord");return e}export function encodeToolExecutionValue(e){const t={ancestors:new WeakMap,diagnostics:[]},n=R(e,"$",t);return 0===t.diagnostics.length?{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.json,value:n}:{version:1,encoding:TOOL_EXECUTION_VALUE_ENCODINGS.diagnostic,value:n,diagnostics:t.diagnostics}}export function createToolExecutionRecord(e){return{version:1,id:void 0!==e.id?parseToolExecutionRecordId(e.id):createToolExecutionRecordId(),toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt??(new Date).toISOString(),input:encodeToolExecutionValue(e.input),raw:encodeToolExecutionValue(e.result.raw),model:X(e.result.model),display:encodeToolExecutionValue(e.result.display),outcome:V(e.result.outcome)}}export function toRedactedToolExecutionRecordSummary(e){return{version:e.version,id:e.id,toolCallId:e.toolCallId,agentName:e.agentName,toolName:e.toolName,createdAt:e.createdAt,input:i,raw:i,model:Z(e.model),display:J(e.display),outcome:q(e.outcome),...isPersistedToolExecutionRecord(e)?{persistence:e.persistence}:{}}}
@@ -66,6 +66,8 @@ export type SessionEvent = {
66
66
  readonly toolName: string;
67
67
  readonly input: unknown;
68
68
  readonly reason?: string;
69
+ /** Model-authored display aid. It never changes approval policy or command execution. */
70
+ readonly explanation?: string;
69
71
  } | {
70
72
  readonly type: "step-finish";
71
73
  readonly finishReason: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/runtime",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,11 +30,11 @@
30
30
  "@modelcontextprotocol/sdk": "^1.12.0",
31
31
  "ai": "^7.0.9",
32
32
  "zod": "^3.25.76",
33
- "@roll-agent/protocol": "0.1.0"
33
+ "@roll-agent/protocol": "0.3.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^22.0.0",
37
- "@roll-agent/core": "0.24.0"
37
+ "@roll-agent/core": "0.26.0"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/obfuscate.mjs",