@blocks-network/sdk 1.0.13 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -480,7 +480,7 @@ import { TaskClient, textPart } from '@blocks-network/sdk';
480
480
  const session = await client.sendMessage({
481
481
  agentName: 'multi_stream_agent',
482
482
  requestParts: [textPart('start')],
483
- stream: true, // request-task streaming is opt-in (BLOCKS-181)
483
+ stream: true, // request-task streaming is opt-in
484
484
  });
485
485
 
486
486
  // Option 1 — react to each stream as it is announced
@@ -513,7 +513,7 @@ loops. Tune the window per session:
513
513
  const session = await client.sendMessage({
514
514
  agentName: 'llm_streamer',
515
515
  requestParts: [textPart('stream please')],
516
- stream: true, // request-task streaming is opt-in (BLOCKS-181)
516
+ stream: true, // request-task streaming is opt-in
517
517
  drainWindowMs: 5_000, // 5 seconds
518
518
  });
519
519
 
@@ -1 +1 @@
1
- import{CURRENT_PROTOCOL_VERSION as e,PROTOCOL_VERSION_HEADER as t}from"./protocol-version.js";import{captureAffinity as r,injectAffinity as s}from"./write-affinity.js";export class AgentAuthFatalError extends Error{constructor(e){super(e),this.name="AgentAuthFatalError"}}export class AgentAuth{constructor(e,t){if(this.accessToken=null,this.refreshTokenValue=null,this.refreshLock=null,this.registrationPayload=null,!e)throw new Error("apiKey is required");if(!t)throw new Error("baseUrl is required");this.apiKey=e,this.baseUrl=t.replace(/\/+$/,"")}async init(a){const o=`${this.baseUrl}/api/v1/auth/agent/connect`;this.registrationPayload=a;const i={Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json",[t]:e};s(i);const n=await fetch(o,{method:"POST",headers:i,body:JSON.stringify(a)});if(r(n.headers),!n.ok){const e=await n.json().catch(()=>({}));throw new AgentAuthFatalError(`Agent registration failed: ${e.error??`HTTP ${n.status}`}`)}const h=await n.json();return this.accessToken=h.accessToken,this.refreshTokenValue=h.refreshToken,h}getAccessToken(){return this.accessToken}getApiKey(){return this.apiKey}async refresh(){if(this.refreshLock)await this.refreshLock;else{this.refreshLock=this._doRefresh();try{await this.refreshLock}finally{this.refreshLock=null}}}async _doRefresh(){const e=`${this.baseUrl}/api/v1/auth/agent/refresh`,t={Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"};s(t);const a=await fetch(e,{method:"POST",headers:t,body:JSON.stringify({refreshToken:this.refreshTokenValue})});if(r(a.headers),a.ok){const e=await a.json();return this.accessToken=e.accessToken,void(this.refreshTokenValue=e.refreshToken)}const o=await a.json().catch(()=>({}));if("REFRESH_TOKEN_INVALID"!==o.code){if("API_KEY_INVALID"===o.code)throw new AgentAuthFatalError(`API key invalid or revoked: ${o.error??"API_KEY_INVALID"}`);throw new Error(`Token refresh failed: ${o.error??`HTTP ${a.status}`}`)}if(!this.registrationPayload)throw new Error("Cannot re-register: no registration payload stored. Call init() with a payload first.");await this.init(this.registrationPayload)}async authenticatedFetch(a,o){const i=async i=>{const n=new Headers(o?.headers);n.set("Authorization",`Bearer ${i}`),n.has(t)||n.set(t,e);const h={};s(h);for(const[e,t]of Object.entries(h))n.set(e,t);const c=await fetch(a,{...o,headers:n});return r(c.headers),c},n=this.accessToken;if(!n)throw new Error("AgentAuth not initialized — call init() first");const h=await i(n);if(401!==h.status)return h;await this.refresh();const c=this.accessToken;if(!c)throw new Error("Failed to obtain access token after refresh");return i(c)}}
1
+ import{CURRENT_PROTOCOL_VERSION as e,PROTOCOL_VERSION_HEADER as t}from"./protocol-version.js";import{captureAffinity as r,injectAffinity as s}from"./write-affinity.js";const o="API_KEY_INVALID",a="AGENT_FORCED_OFFLINE";export class AgentAuthFatalError extends Error{constructor(e){super(e),this.name="AgentAuthFatalError"}}export class AgentAuth{constructor(e,t){if(this.accessToken=null,this.refreshTokenValue=null,this.refreshLock=null,this.registrationPayload=null,!e)throw new Error("apiKey is required");if(!t)throw new Error("baseUrl is required");this.apiKey=e,this.baseUrl=t.replace(/\/+$/,"")}async init(i){const n=`${this.baseUrl}/api/v1/auth/agent/connect`;this.registrationPayload=i;const h={Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json",[t]:e};s(h);const c=await fetch(n,{method:"POST",headers:h,body:JSON.stringify(i)});if(r(c.headers),!c.ok){const e=await c.json().catch(()=>({}));if(e.code===a)throw new AgentAuthFatalError("Agent forced offline by an administrator. It must be re-enabled before it can reconnect.");if(e.code===o)throw new AgentAuthFatalError(`API key invalid or revoked: ${e.error??"API_KEY_INVALID"}`);throw new Error(`Agent registration failed: ${e.error??`HTTP ${c.status}`}`)}const f=await c.json();return this.accessToken=f.accessToken,this.refreshTokenValue=f.refreshToken,f}getAccessToken(){return this.accessToken}getApiKey(){return this.apiKey}async refresh(){if(this.refreshLock)await this.refreshLock;else{this.refreshLock=this._doRefresh();try{await this.refreshLock}finally{this.refreshLock=null}}}async _doRefresh(){const e=`${this.baseUrl}/api/v1/auth/agent/refresh`,t={Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"};s(t);const i=await fetch(e,{method:"POST",headers:t,body:JSON.stringify({refreshToken:this.refreshTokenValue})});if(r(i.headers),i.ok){const e=await i.json();return this.accessToken=e.accessToken,void(this.refreshTokenValue=e.refreshToken)}const n=await i.json().catch(()=>({}));if("REFRESH_TOKEN_INVALID"!==n.code){if(n.code===a)throw new AgentAuthFatalError("Agent forced offline by an administrator. It must be re-enabled before it can reconnect.");if(n.code===o)throw new AgentAuthFatalError(`API key invalid or revoked: ${n.error??"API_KEY_INVALID"}`);throw new Error(`Token refresh failed: ${n.error??`HTTP ${i.status}`}`)}if(!this.registrationPayload)throw new Error("Cannot re-register: no registration payload stored. Call init() with a payload first.");await this.init(this.registrationPayload)}async authenticatedFetch(o,a){const i=async i=>{const n=new Headers(a?.headers);n.set("Authorization",`Bearer ${i}`),n.has(t)||n.set(t,e);const h={};s(h);for(const[e,t]of Object.entries(h))n.set(e,t);const c=await fetch(o,{...a,headers:n});return r(c.headers),c},n=this.accessToken;if(!n)throw new Error("AgentAuth not initialized — call init() first");const h=await i(n);if(401!==h.status)return h;await this.refresh();const c=this.accessToken;if(!c)throw new Error("Failed to obtain access token after refresh");return i(c)}}
@@ -1 +1 @@
1
- import{StreamClient as e}from"../stream/index.js";import{buildArtifactRef as t,shouldInlineArtifact as a,decodeInlineArtifact as s}from"./artifacts.js";import{createPubNubClient as n}from"./pubnub-client.js";import{createChannelManager as r}from"./channel-manager.js";import{toPayload as o}from"./pubnub-types.js";import{connectAgent as i,getAgent as d}from"./agent-registry.js";import{parseStreamSetupResponse as c,parseStreamSetupError as l}from"./stream-setup-helper.js";import{DEFAULTS as u}from"../defaults.js";import{getEnv as m}from"../env.js";import{log as f,isDebugSubsystemEnabled as p,_resolveLogLevel as k,_LOG_LEVEL_ORDER as g}from"./logger.js";import{mapTransportCategory as h,mapTransportOperation as y,isAccessDeniedStatus as I,DEGRADED_TRANSPORT_CATEGORIES as b,RESTORED_TRANSPORT_CATEGORIES as w}from"./transport-categories.js";import{uploadFile as v}from"./file-upload.js";import{StaticAuthProvider as S}from"./auth-provider.js";import{StreamRegistry as T}from"./stream-registry.js";import{CredentialCache as C}from"./credential-cache.js";import{createStreamObject as $,createExternalStreamObject as _,runOnActivate as K}from"./stream-context.js";import{TaskClient as A}from"./task-client.js";import{ConsumerAuth as E}from"./consumer-auth.js";import{fetchCdmConfig as x}from"./cdm-config.js";import{AgentAuth as M,AgentAuthFatalError as N}from"./agent-auth.js";import{CURRENT_PROTOCOL_VERSION as R,SUPPORTED_PROTOCOL_VERSIONS as j,isProtocolVersionSupported as P}from"./protocol-version.js";import{isProfilingEnabled as U,logDispatchTiming as q}from"./profiling.js";const D=new Map,B=async(e,t,a,s,n,i=R)=>{const d=r(s);try{await e.publish({channel:d.taskChannel(t,a),message:o({...n,protocolVersion:i}),storeInHistory:!0,sendByPost:!0,meta:o({agentName:s,taskId:t,protocolVersion:i})})}catch{}};export function _isDiagEntryStale(e){const{lastStatusAt:t,lastCategory:a,now:s,thresholdMs:n}=e;return null!==t&&("connected"!==a&&s-t>n)}const L=(e,t,a)=>f("[AgentInstance]",e,t,a);export function resolveMaxRunningTimeSec(e,t){return void 0!==e&&void 0!==t&&e!==t&&f("[AgentInstance]","info",`opts.maxRunningTimeSec (${e}) overrides card.runtime.maxRunningTimeSec (${t})`,{event:"max_running_time_override",optsValue:e,cardValue:t}),e??t}export function computeStreamDurationMinutes(e,t,a){return"number"==typeof e?e:t?60:Math.ceil((a??3600)/60)}const V=async(e,s,n,r,o,i,d,c,l)=>{const u={type:"artifact",taskId:s};if(c&&(u.outputId=c),a(o.length))await B(e,s,n,r,{...u,artifactRef:t({data:o,mimeType:i,fileName:d})});else{if(!l)throw new Error("Artifact size exceeds the inline threshold but no baseUrl is configured. Set baseUrl in AgentInstanceOptions to enable the pre-signed URL upload flow.");await v(l,{role:"provider-output",taskId:s,fileName:d??`${s}-artifact`,fileSize:o.length,mimeType:i,outputId:c},o)}};async function F(e,t){const a=`setup.${t.orgId}.${t.taskId}`,s={type:"stream_setup",taskId:t.taskId,orgId:t.orgId,agentName:t.agentName,streamId:t.streamId,channel:t.channel,direction:t.direction,format:t.format,taskKind:t.taskKind,durationMinutes:t.durationMinutes,affinity:t.affinity,declaredStream:t.declaredStream??"_default",protocolVersion:R};t.phase&&(s.phase=t.phase),t.metadata&&(s.metadata=t.metadata);try{throw await e.publish({channel:a,message:o(s),storeInHistory:!1,sendByPost:!0}),new Error("stream_setup publish should have been aborted by Function")}catch(e){const t=l(e);if(t)throw new Error(`Stream setup failed: [${t.code}] ${t.message}`);const a=c(e);if(a)return{token:a.token,tokenTtlMinutes:a.tokenTtlMinutes};throw e}}export const startAgentInstance=async t=>{const a=t.agentName;if(!a)throw new Error("agentName is required: provide opts.agentName");if(!/^[a-zA-Z0-9_]+$/.test(a))throw new Error("agentName must contain only alphanumeric characters and underscores (no hyphens)");const o=r(a),c=t.instanceId??`AG-${a}-${crypto.randomUUID()}`,l=t.token;let f,v=null,O="playground",z=O;if(t.pubnub){const e=t.pubnub._configuration?.keySet,a={publishKey:e?.publishKey??"",subscribeKey:e?.subscribeKey??""};f={playground:a,network:a}}else v=await x(t.cdmUrl),f={playground:v.playground,network:v.network},L("info","CDM config loaded — environment switching enabled",{event:"cdm_config_loaded"});const H=m("BLOCKS_API_KEY");if(!H)throw new Error("BLOCKS_API_KEY is required. Run 'blocks login --write-env' to set up credentials.");let G;const Y=t.baseUrl??v?.api.baseUrl;let W,Z;if(H&&Y&&(G=new M(H,Y),L("info","AgentAuth created — will authenticate via connect",{event:"agent_auth_created"})),v){const e=t.baseUrl??v.api.baseUrl,s=await d(a,{baseUrl:e,apiKey:H});if(!s)throw new Error(`[AgentInstance] Agent "${a}" not found in registry. Register the agent first — \`blocks register\` (private + free, recommended) or \`blocks publish\` (public/paid) — before starting an instance.`);if(!s.billingMode)throw new Error(`[AgentInstance] Agent "${a}" registry entry is missing billingMode. Re-register the agent so the registry persists an explicit billingMode.`);W=s.billingMode,Z=s.listing,O="paid"===W?"network":"playground",L("info",`registry billingMode: ${W} — using ${O} environment`,{event:"registry_billing_mode_resolved",billingMode:W,environment:O}),z=O}const J=W??"free",Q=[];let X=null;const ee=p("diagnostics"),te=()=>{let e=!1;return{status:t=>{const a=h(t),s=b.has(a);s&&!e?(e=!0,L("warn",`connectivity degraded: ${a}`,{event:"transport_degraded",category:a,instanceId:c})):!s&&e&&w.has(a)&&(e=!1,L("info","connectivity restored",{event:"transport_restored",category:a,instanceId:c}))}}},ae=(e,t)=>{if(!ee)return;const a={label:e,pn:t,listener:void 0,lastStatusAt:null,lastConnectedAt:null,lastMessageAt:null,lastCategory:null,lastOperation:null,lastStatusCode:null};a.listener=(e=>({status:t=>{const a=t,s=h(a),n=y("string"==typeof a.operation?a.operation:void 0),r="number"==typeof a.statusCode?a.statusCode:null,o=Date.now(),i=e.lastCategory;e.lastStatusAt=o,e.lastCategory=s,e.lastOperation=n,e.lastStatusCode=r,"connected"===s&&(e.lastConnectedAt=o),L("debug","transport status event",{event:"transport_status",client:e.label,category:s,operation:n,statusCode:r,error:Boolean(a.error),affectedChannels:Array.isArray(a.affectedChannels)?a.affectedChannels.length:void 0,instanceId:c}),s&&s!==i&&L("info",`transport status transition [${e.label}]: ${i??"(none)"} -> ${s}`,{event:"transport_status_transition",client:e.label,from:i,to:s,operation:n,statusCode:r,instanceId:c})},message:()=>{e.lastMessageAt=Date.now()}}))(a),Q.push(a),t.addListener(a.listener)},se=e=>{if(!ee)return;const t=Q.findIndex(t=>t.pn===e);if(t<0)return;const a=Q[t];try{e.removeListener(a.listener)}catch{}Q.splice(t,1)};let ne,re=!1;if(t.pubnub)ne=t.pubnub,re=!1,l&&ne.setToken(l);else{const e=f[z];ne=n({publishKey:e.publishKey,subscribeKey:e.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0}),re=!0,l&&ne.setToken(l)}let oe=f[z].subscribeKey,ie=f[z].publishKey;const de=Y?new E({apiKey:H,baseUrl:Y}):void 0,ce=new A({billingMode:J,subscribeKey:oe,publishKey:ie,authProvider:de,baseUrl:Y,createPubNub:()=>{const e=n({publishKey:ie,subscribeKey:oe,userId:`${c}-taskclient`,subscribeRetryUnbounded:!1});return ae("consumer-task",e),e}}),le=t.concurrency??u.concurrency,ue=t.expectedInstances??u.expectedInstances,me=t.maxPendingBacklog,fe=resolveMaxRunningTimeSec(t.maxRunningTimeSec,t.card?.runtime?.maxRunningTimeSec),pe=Date.now();let ke=0;const ge=new T,he=new C,ye=new Map,Ie=new Map,be=new Map,we=new Map,ve=new Map,Se=new Set,Te=new Set,Ce=new Map,$e=new Set,_e=new Map;let Ke,Ae;t.logChannel;const Ee=async()=>{if("function"==typeof ne.setState&&Ae)try{await ne.setState({channels:[Ae],state:{instanceId:c,activeTasks:ke,activeStreams:ge.activeStreamCount,concurrency:le,startedAt:pe,preferredProtocolVersion:R,protocolVersions:[...j]}})}catch{}},xe=(e,t)=>{const a=ye.get(e);a&&(a.delete(t),0===a.size&&ye.delete(e))},Me=async e=>{(e=>{for(const[t,a]of ye)a.has(e)&&(a.delete(e),0===a.size&&ye.delete(t))})(e);const t=ge.releaseAllForTask(e);for(const e of t)if(e.streamClient)try{await e.streamClient.end()}catch{}},Ne=async(e,t)=>{xe(e,t);const a=ge.get(e);if(0===ge.release(e,t)&&a?.streamClient){L("info","last-ref teardown",{event:"stream_registry_last_ref_teardown",streamId:e,affinity:a.affinity,releasingTaskId:t});try{await a.streamClient.end()}catch{}}Ee().catch(()=>{})},Re=async(e,t)=>{const a=ge.forceRemove(e);if((e=>{ye.delete(e)})(e),a){if(a.streamClient)try{await a.streamClient.end()}catch{}for(const e of a.taskIds){const a=he.get(e);if(a){try{const s=f[a.environment??O]??f[O],r=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken),await B(r,e,a.orgId,a.agentName,{type:"terminal",taskId:e,state:"failed",error:t}),r.destroy()}catch{}he.remove(e)}}await Ee()}},je=async(e,t)=>{const a=he.get(e);if(!a)throw new Error(`No cached credentials for task ${e}`);const s=f[a.environment??O]??f[O],r=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken);try{await B(r,e,a.orgId,a.agentName,{...t,type:"terminal",taskId:e})}finally{r.destroy()}await Me(e),he.remove(e),await Ee()},Pe=async(n,r,i,d,c)=>{const u=a,m="pipe"===n.taskKind,f=m?"pipe":"request",p=computeStreamDurationMinutes(n.duration,m,fe);if(m&&"number"==typeof n.durationExpiresAtMs){const e=Math.max(0,n.durationExpiresAtMs-Date.now());if(e>0){const t=setTimeout(()=>{Se.add(n.taskId),c.abort()},e);Ce.set(n.taskId,t)}}await B(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,progress:0,state:"running"});const k=Date.now();_e.set(n.taskId,0);let g=0,h=null,y=null;const I=()=>{if(null===h)return;const e=h;h=null,g=Date.now(),B(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,message:e}).catch(()=>{})},b={taskId:n.taskId,requestParts:n.requestParts??[],reportStatus:e=>{we.set(n.taskId,e);const t=Date.now();if(t-g>=1e3)h=null,g=t,B(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,message:e}).catch(()=>{});else if(h=e,!y){y=setTimeout(()=>{y=null,I()},1e3-(t-g))}},get isCancelled(){return c.signal.aborted},get isExpired(){return Se.has(n.taskId)},hasStream:!!n.hasStream,cancelSignal:c.signal,taskClient:ce,consumerPublicKey:n.consumerPublicKey,downloadInputArtifact:async e=>{const t=e.artifactRef;if(!t)throw new Error(`Request part has no artifactRef (partId: ${e.partId??"none"})`);if("inline"===t.kind)return Buffer.from(s(t));if(!t.channel||!t.fileId||!t.fileName)throw new Error("File artifactRef missing channel, fileId, or fileName");if(!r.downloadFile)throw new Error("PubNub client does not support downloadFile");const a=await r.downloadFile({channel:t.channel,id:t.fileId,name:t.fileName}),n=a?.data;if(Buffer.isBuffer(n))return n;if(n instanceof Uint8Array)return Buffer.from(n);if(n&&"function"==typeof n.arrayBuffer){const e=await n.arrayBuffer();return Buffer.from(e)}throw new Error("Unexpected downloadFile response format")},publishArtifact:async(e,t)=>{const a="string"==typeof e?Buffer.from(e,"utf-8"):e,s=t?.mimeType??"application/octet-stream",o=Y?{baseUrl:Y,authProvider:l?new S(l):void 0,agentAuth:G}:void 0;await V(r,n.taskId,d,u,a,s,t?.fileName,t?.outputId,o)},createStream:async a=>{if(void 0!==a&&("object"!=typeof a||null===a||Array.isArray(a)))throw new TypeError("createStream expects an options object. The positional streamId argument was removed; move any declared-stream key into CreateStreamOptions.declaredStream. See SDK_CONTRACT §8.2.1.");if(!n.hasStream)throw new Error("Streaming was not negotiated for this task. Ensure the agent card is registered with streaming capability.");const s=t.card.streams;if(!s||0===Object.keys(s).length)throw new Error("Agent card has no streams block. Streaming requires declared streams in the card.");const i=Object.keys(s);let c;if(a?.declaredStream)c=a.declaredStream;else{if(1!==i.length)throw new Error(`Card declares multiple streams (${i.join(", ")}). Specify declaredStream in CreateStreamOptions.`);c=i[0]}const l=s[c];if(!l)throw new Error(`Stream "${c}" is not declared in the agent card. Available streams: ${i.join(", ")}`);const k=a?.direction??l.direction,g=a?.format??l.format;if(a?.direction&&a.direction!==l.direction)throw new Error(`Direction "${a.direction}" conflicts with card declaration "${l.direction}" for stream "${c}".`);if(a?.format&&a.format!==l.format)throw new Error(`Format "${a.format}" conflicts with card declaration "${l.format}" for stream "${c}".`);const h=a?.external??!1,y=a?.metadata,I=a?.onActivate,b=l.affinity??"dedicated";if("shared"===b&&h)throw new Error(`Shared-affinity external streams are not supported. Declared stream '${c}' has affinity: 'shared' and createStream was called with external: true. Shared affinity requires a single SDK-managed writer with per-task ref-counting; external streams delegate the writer entirely. Use affinity: 'dedicated' with external: true, or affinity: 'shared' without external.`);if(!m){if("shared"===b)throw new Error(`Shared-affinity streams are not supported on request tasks. Declared stream '${c}' has affinity: 'shared'. Request tasks are single-shot; cross-task broadcast is inherently a pipe-task concept. Use affinity: 'dedicated' or remove 'request' from the agent card's taskKinds.`);if("outbound"!==k)throw new Error("Request tasks only support outbound streams");if(h)throw new Error("Request tasks cannot use external streams")}let w;if("shared"===b)w=c;else{const e=(_e.get(n.taskId)??0)+1;_e.set(n.taskId,e),w=`${n.taskId}-${e}`}const v=o.streamChannel(w);if("shared"===b){const e=ye.get(w)?.get(n.taskId);if(e)return e}const{entry:S,isNew:T,isNewForTask:C}=ge.acquire(w,n.taskId,{direction:k,format:g,external:h,affinity:b});he.addStream(n.taskId,w);const A=e=>{if("shared"!==b)return;let t=ye.get(w);t||(t=new Map,ye.set(w,t)),t.set(n.taskId,e)};if(!C){S.setupPromise&&await S.setupPromise;const e=ye.get(w)?.get(n.taskId);if(e)return e;if(S.streamClient){const e=$(w,S.streamClient,n.taskId,{releaseStream:Ne});return A(e),e}throw new Error(`Stream "${w}" same-task reacquire reached an impossible state: setup completed but no client installed. This indicates a logic error in the shared-stream handle cache.`)}if(!T)try{if(S.setupPromise&&await S.setupPromise,"shared"===b&&await(async()=>{await F(r,{taskId:n.taskId,orgId:d,agentName:u,streamId:w,channel:v,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:b,phase:"activate",metadata:y,declaredStream:c})})(),S.streamClient){const e=$(w,S.streamClient,n.taskId,{releaseStream:Ne});return A(e),e}if(h)return _(w,v,"",async()=>{throw new Error("External stream already activated")});throw new Error(`Stream "${w}" exists but has no client`)}catch(e){xe(w,n.taskId);try{await ge.release(w,n.taskId)}catch{}throw e}let E,x;S.setupPromise=new Promise((e,t)=>{E=e,x=t}),S.setupPromise.catch(()=>{});const M=h?"token_request":"embedded";let N;try{N=await F(r,{taskId:n.taskId,orgId:d,agentName:u,streamId:w,channel:v,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:b,phase:M,metadata:y,declaredStream:c})}catch(e){x(e),xe(w,n.taskId);try{await ge.release(w,n.taskId)}catch{}throw e}if(h){E(),S.setupPromise=null;const e=N.token??"";return _(w,v,e,async e=>{await F(r,{taskId:n.taskId,orgId:d,agentName:u,streamId:w,channel:v,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:b,phase:"activate",metadata:e?.metadata??y,declaredStream:c})})}const R=N.token;if(!R){const e=new Error("No T7a token received from stream setup handshake");x(e),xe(w,n.taskId);try{await ge.release(w,n.taskId)}catch{}throw e}const j=new e({subscribeKey:oe,publishKey:ie,token:R,agentName:u,streamId:w,channel:v,format:g,direction:k,affinity:b,gating:m,bundleSizeBytes:a?.bundleSizeBytes,maxLatencyMs:a?.maxLatencyMs});S.streamClient=j;const P=$(w,j,n.taskId,{releaseStream:Ne});if(A(P),E(),S.setupPromise=null,"outbound"===k||"bidirectional"===k){const e=a?.subscribeGraceMs??1e3;e>0&&await new Promise(t=>setTimeout(t,e))}return I&&(S.activated=!0,S.activatePromise=K(w,P,I,Re)),P}};let w;const v=Date.now(),T=D.get(n.taskId);void 0!==T&&(q(n.taskId,{receivedMs:T,runningMs:k,handlerMs:v}),D.delete(n.taskId));try{w=t.handler?await t.handler(n,b):void 0}finally{_e.delete(n.taskId),y&&(clearTimeout(y),y=null),I()}const C=Y?{baseUrl:Y,authProvider:l?new S(l):void 0,agentAuth:G}:void 0;if(w?.artifacts&&w.artifacts.length>0)for(const e of w.artifacts){const t="string"==typeof e.data?Buffer.from(e.data,"utf-8"):e.data;await V(r,n.taskId,d,u,t,e.mimeType,e.fileName,e.outputId,C)}const A=c.signal.aborted;if(m){const e=Se.has(n.taskId),t=Te.has(n.taskId);if(A||e||t){await Me(n.taskId);const a=A&&!e?"canceled":"completed";await B(r,n.taskId,d,u,{type:"terminal",taskId:n.taskId,state:a,...e?{completionReason:"duration_expired"}:{},...t?{reason:"terminated"}:{}}),he.remove(n.taskId),L("info",`Task ${n.taskId} ${a}`,{taskId:n.taskId,agentName:u,owner:i})}else L("info",`Task ${n.taskId} handler returned (pipe, no auto-terminal)`,{taskId:n.taskId,agentName:u,owner:i})}else{const e=Te.has(n.taskId);await Me(n.taskId);const t=A?"canceled":"completed";await B(r,n.taskId,d,u,{type:"terminal",taskId:n.taskId,state:t,...e?{reason:"terminated"}:{}}),he.remove(n.taskId),L("info",`Task ${n.taskId} ${t}`,{taskId:n.taskId,agentName:u,owner:i})}},Ue=(()=>{let e=!1;return(t,a)=>{const s=h(t),n="number"==typeof t.statusCode?t.statusCode:null;if(L("debug","access-denied handler invoked",{event:"access_denied_handler_invoked",category:s,operation:y(a),statusCode:n,alreadyHandled:e,instanceId:c}),I(t)&&!e){e=!0,L("error","access denied — destroying control client (agent will go silent)",{event:"access_denied_destroy",category:s,operation:y(a),statusCode:n,instanceId:c,controlChannel:Ae}),L("error","access token expired or revoked — agent is no longer receiving tasks. Re-register the agent to resume.",{event:"access_denied_user_message",instanceId:c});try{ne.destroy()}catch{}}}})();let qe=te();const De={status:e=>{const t=e,a=String(t.category??""),s=String(t.operation??"");"PNConnectedCategory"===a&&L("debug","control client connected",{event:"control_connected",controlChannel:Ae}),Ue(t,s)},message:e=>{const s=e.message;if(!s)return;if("SwitchEnvironment"===s.type){const e=s,t=e.environment;return void(t&&t!==z&&t in f&&((e,t)=>{if(L("info",`SwitchEnvironment requested: ${z} -> ${e}`,{pamToken:t?"present":"absent"}),!t)return void L("error","SwitchEnvironment rejected: pamToken is required but was absent. The backend must include a pamToken in SwitchEnvironment messages.");const a=ne,s=re;try{a.removeListener(De),a.removeListener(qe),Ae&&a.unsubscribe({channels:[Ae]})}catch{}se(a);const r=f[e];if(ne=n({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0}),re=!0,Ke=void 0,ne.setToken(t),0!==ue&&"function"==typeof ne.setFilterExpression&&ne.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),qe=te(),ne.addListener(De),ne.addListener(qe),ae("control",ne),Ae&&ne.subscribe({channels:[Ae]}),s)try{a.destroy()}catch{}z=e,oe=r.subscribeKey,ie=r.publishKey,ce.updateKeys(r.subscribeKey,r.publishKey),L("info",`Switched to ${e} environment`),Ee().catch(()=>{})})(t,e.pamToken))}(async(e,s)=>{if("StartTask"===e.type){U()&&D.set(e.taskId,Date.now());const r=z,o=((e,t)=>{if("string"==typeof e&&e.length>0)return e;const a=t?.sub;return"string"==typeof a&&a.length>0?a:"anonymous"})(e.ownerId,e.callerClaims),i=e.orgId??o;if($e.has(e.taskId))return void L("warn",`Ignoring duplicate StartTask for in-flight task ${e.taskId}`,{taskId:e.taskId});const d=Boolean(s?.broadcast);if(e.protocolVersion&&!P(e.protocolVersion))return d?void L("debug",`Ignoring broadcast StartTask ${e.taskId} with unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId}):(await B(ne,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"unsupported_protocol_version"},e.protocolVersion),void L("warn",`Task ${e.taskId} rejected: unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId,instanceId:c}));if("pipe"===(e.taskKind||"request")){const t=e.duration,s=e.durationExpiresAtMs;if(null==t||!Number.isInteger(t)||t<1||t>43200||"number"!=typeof s||s<=0)return L("error",`Task ${e.taskId} rejected: pipe StartTask with invalid duration (duration=${t}, durationExpiresAtMs=${s})`,{taskId:e.taskId,instanceId:c}),void await B(ne,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"invalid_start_task"},e.protocolVersion)}const l=ke+ge.activeStreamCount;if(le>0&&l>=le)return d?void L("debug",`Task ${e.taskId} skipped (broadcast, at capacity ${l}/${le})`,{taskId:e.taskId,instanceId:c}):(await B(ne,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"agent_at_capacity"}),void L("warn",`Task ${e.taskId} rejected: agent at capacity`,{taskId:e.taskId,instanceId:c,activeTasks:ke}));Ie.set(e.taskId,o),be.set(e.taskId,i),$e.add(e.taskId),ke++,e.controlToken&&(Ke=e.controlToken);const u=new AbortController;ve.set(e.taskId,u);let m=null;try{const s=f[r];if(!s.publishKey||!s.subscribeKey)throw new Error("Per-task PubNub client requires valid pub/sub keys. When using an injected PubNub client, ensure it was created with publishKey and subscribeKey.");m=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1}),e.writeToken&&m.setToken(e.writeToken),ae(`task-sub:${e.taskId}`,m),he.set(e.taskId,{ownerId:o,orgId:i,writeToken:e.writeToken??"",agentName:a,environment:r}),await Ee();const{writeToken:d,controlToken:l,...p}=e;t.onStartTask?await t.onStartTask(e,m):await Pe(p,m,o,i,u)}catch(s){const n=s?.message??"Agent instance error",r=m??ne;try{await Me(e.taskId)}catch{}await B(r,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:n}),t.onError&&await t.onError(e.taskId,s),L("error",n,{taskId:e.taskId}),he.remove(e.taskId)}finally{const t=Ce.get(e.taskId);if(t&&(clearTimeout(t),Ce.delete(e.taskId)),$e.delete(e.taskId),Ie.delete(e.taskId),be.delete(e.taskId),we.delete(e.taskId),ve.delete(e.taskId),Se.delete(e.taskId),Te.delete(e.taskId),m){se(m);try{m.destroy()}catch{}}ke--,0===ke&&Ke&&ne.setToken(Ke),await Ee()}}else if("CancelTask"===e.type){const t=ve.get(e.taskId);t?(t.abort(),L("info",`Task ${e.taskId} cancel requested (cooperative)`,{taskId:e.taskId,instanceId:c})):he.get(e.taskId)&&await je(e.taskId,{state:"canceled"})}else if("ExpireTask"===e.type)ve.has(e.taskId)?(Se.add(e.taskId),ve.get(e.taskId)?.abort(),L("info",`Task ${e.taskId} expired`,{taskId:e.taskId,instanceId:c})):he.get(e.taskId)&&await je(e.taskId,{state:"completed",completionReason:"duration_expired"});else if("TerminateTask"===e.type)ve.has(e.taskId)?(Te.add(e.taskId),ve.get(e.taskId)?.abort()):he.get(e.taskId)&&await je(e.taskId,{state:"canceled",reason:"terminated"});else if("PauseTask"===e.type){if($e.has(e.taskId)){const t=be.get(e.taskId)??"anonymous";await B(ne,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"paused"})}}else if("ResumeTask"===e.type){if($e.has(e.taskId)){const t=be.get(e.taskId)??"anonymous";await B(ne,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"resumed"})}}else"RetryTask"===e.type&&L("info","RetryTask received (no-op)",{taskId:e.taskId})})(s,e.userMetadata&&"object"==typeof e.userMetadata?e.userMetadata:void 0).catch(e=>{L("error","unhandled error in message handler",{event:"message_handler_error",error:e instanceof Error?e.message:String(e),stack:e instanceof Error?e.stack:void 0})})}};ne.addListener(De),ne.addListener(qe),ae("control",ne),ee&&null===X&&(X=setInterval(()=>{const e=Date.now();let t=!1;const a=Q.map(a=>{const s=null!==a.lastStatusAt?e-a.lastStatusAt:null,n=_isDiagEntryStale({lastStatusAt:a.lastStatusAt,lastCategory:a.lastCategory,now:e,thresholdMs:6e4});n&&(t=!0);const r="function"==typeof a.pn.getSubscribedChannels?a.pn.getSubscribedChannels().length:null;return{client:a.label,lastCategory:a.lastCategory,lastOperation:a.lastOperation,lastStatusCode:a.lastStatusCode,msSinceStatus:s,msSinceConnected:null!==a.lastConnectedAt?e-a.lastConnectedAt:null,msSinceMessage:null!==a.lastMessageAt?e-a.lastMessageAt:null,subscribedChannels:r,stale:n}}),s={event:"transport_alive_snapshot",instanceId:c,clients:a};t?L("info","transport alive snapshot — STALE clients present",s):L("debug","transport alive snapshot",s)},1e4),"function"==typeof X.unref&&X.unref()),ee&&L("info","transport diagnostics armed",{event:"transport_diagnostics_armed",snapshotIntervalMs:1e4,staleThresholdMs:6e4,instanceId:c}),i(a,{instanceId:c,billingMode:J,description:t.description,scaling:{expectedInstances:ue,concurrency:le,maxPendingBacklog:me,maxRunningTimeSec:fe},card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:Z??t.listing,actor:`agent-instance:${c}`,baseUrl:t.baseUrl??v?.api.baseUrl,agentAuth:G}).then(e=>{if(L("info",`registered agent: ${a} (instance: ${c})`,{event:"agent_registered",agentName:a,instanceId:c}),!e.controlChannel)throw new Error("Connect response missing controlChannel — server may be outdated");Ae=e.controlChannel,e.pamToken&&ne.setToken(e.pamToken),0!==ue&&"function"==typeof ne.setFilterExpression&&ne.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),ne.subscribe({channels:[Ae]}),Ee().catch(()=>{})}).catch(e=>{const t=e instanceof Error?e.message:String(e);L("error",`failed to register agent: ${a} — ${t}`,{event:"agent_registration_failed",agentName:a,instanceId:c,error:t}),e instanceof N&&process.exit(1)}),L("info",`Agent instance ${c} started (agent name: ${a})`,{agentName:a,event:"agent_instance_started",instanceId:c});return{stop:()=>{try{null!==X&&(clearInterval(X),X=null);for(const e of Q)try{e.pn.removeListener(e.listener)}catch{}Q.length=0,ee&&L("info","transport diagnostics disarmed",{event:"transport_diagnostics_disarmed",instanceId:c}),de?.destroy(),ce.destroy(),ne.removeListener(De),ne.removeListener(qe),Ae&&ne.unsubscribe({channels:[Ae]}),re&&ne.destroy();for(const e of ge.streamIds()){const t=ge.get(e);if(t?.streamClient)try{t.streamClient.end()}catch{}}ge.clear(),ye.clear(),he.clear()}catch{}},agentName:a,instanceId:c,get controlChannel(){return Ae},get pubnub(){return ne},get subscribeKey(){return f[z].subscribeKey},taskClient:ce,publishTerminal:je,failStream:Re,cdmConfig:v}};export const artifactUtils={buildArtifactRef:t,shouldInlineArtifact:a};
1
+ import{StreamClient as e}from"../stream/index.js";import{buildArtifactRef as t,shouldInlineArtifact as a,decodeInlineArtifact as s}from"./artifacts.js";import{createPubNubClient as n}from"./pubnub-client.js";import{createChannelManager as r}from"./channel-manager.js";import{toPayload as o}from"./pubnub-types.js";import{connectAgent as i,getAgent as d}from"./agent-registry.js";import{parseStreamSetupResponse as c,parseStreamSetupError as l}from"./stream-setup-helper.js";import{DEFAULTS as u}from"../defaults.js";import{getEnv as m}from"../env.js";import{log as f,isDebugSubsystemEnabled as p,_resolveLogLevel as k,_LOG_LEVEL_ORDER as g}from"./logger.js";import{mapTransportCategory as h,mapTransportOperation as y,isAccessDeniedStatus as I,DEGRADED_TRANSPORT_CATEGORIES as b,RESTORED_TRANSPORT_CATEGORIES as w}from"./transport-categories.js";import{uploadFile as v}from"./file-upload.js";import{StaticAuthProvider as S}from"./auth-provider.js";import{StreamRegistry as T}from"./stream-registry.js";import{CredentialCache as C}from"./credential-cache.js";import{createStreamObject as $,createExternalStreamObject as _,runOnActivate as K}from"./stream-context.js";import{TaskClient as E}from"./task-client.js";import{ConsumerAuth as A}from"./consumer-auth.js";import{fetchCdmConfig as x}from"./cdm-config.js";import{AgentAuth as M,AgentAuthFatalError as N}from"./agent-auth.js";import{CURRENT_PROTOCOL_VERSION as R,SUPPORTED_PROTOCOL_VERSIONS as j,isProtocolVersionSupported as P}from"./protocol-version.js";import{isProfilingEnabled as U,logDispatchTiming as q}from"./profiling.js";const D=new Map,B=async(e,t,a,s,n,i=R)=>{const d=r(s);try{await e.publish({channel:d.taskChannel(t,a),message:o({...n,protocolVersion:i}),storeInHistory:!0,sendByPost:!0,meta:o({agentName:s,taskId:t,protocolVersion:i})})}catch{}};export function _isDiagEntryStale(e){const{lastStatusAt:t,lastCategory:a,now:s,thresholdMs:n}=e;return null!==t&&("connected"!==a&&s-t>n)}const L=(e,t,a)=>f("[AgentInstance]",e,t,a);function V(e,t){if(!(e instanceof N))return!1;L("error",`Fatal auth error (${t}) — shutting down: ${e.message}`,{event:"agent_auth_fatal",context:t,error:e.message}),process.exit(1)}export function resolveMaxRunningTimeSec(e,t){return void 0!==e&&void 0!==t&&e!==t&&f("[AgentInstance]","info",`opts.maxRunningTimeSec (${e}) overrides card.runtime.maxRunningTimeSec (${t})`,{event:"max_running_time_override",optsValue:e,cardValue:t}),e??t}export function computeStreamDurationMinutes(e,t,a){return"number"==typeof e?e:t?60:Math.ceil((a??3600)/60)}const F=async(e,s,n,r,o,i,d,c,l)=>{const u={type:"artifact",taskId:s};if(c&&(u.outputId=c),a(o.length))await B(e,s,n,r,{...u,artifactRef:t({data:o,mimeType:i,fileName:d})});else{if(!l)throw new Error("Artifact size exceeds the inline threshold but no baseUrl is configured. Set baseUrl in AgentInstanceOptions to enable the pre-signed URL upload flow.");await v(l,{role:"provider-output",taskId:s,fileName:d??`${s}-artifact`,fileSize:o.length,mimeType:i,outputId:c},o)}};async function O(e,t){const a=`setup.${t.orgId}.${t.taskId}`,s={type:"stream_setup",taskId:t.taskId,orgId:t.orgId,agentName:t.agentName,streamId:t.streamId,channel:t.channel,direction:t.direction,format:t.format,taskKind:t.taskKind,durationMinutes:t.durationMinutes,affinity:t.affinity,declaredStream:t.declaredStream??"_default",protocolVersion:R};t.phase&&(s.phase=t.phase),t.metadata&&(s.metadata=t.metadata);try{throw await e.publish({channel:a,message:o(s),storeInHistory:!1,sendByPost:!0}),new Error("stream_setup publish should have been aborted by Function")}catch(e){const t=l(e);if(t)throw new Error(`Stream setup failed: [${t.code}] ${t.message}`);const a=c(e);if(a)return{token:a.token,tokenTtlMinutes:a.tokenTtlMinutes};throw e}}export const startAgentInstance=async t=>{const a=t.agentName;if(!a)throw new Error("agentName is required: provide opts.agentName");if(!/^[a-zA-Z0-9_]+$/.test(a))throw new Error("agentName must contain only alphanumeric characters and underscores (no hyphens)");const o=r(a),c=t.instanceId??`AG-${a}-${crypto.randomUUID()}`,l=t.token;let f,v=null,N="playground",z=N;if(t.pubnub){const e=t.pubnub._configuration?.keySet,a={publishKey:e?.publishKey??"",subscribeKey:e?.subscribeKey??""};f={playground:a,network:a}}else v=await x(t.cdmUrl),f={playground:v.playground,network:v.network},L("info","CDM config loaded — environment switching enabled",{event:"cdm_config_loaded"});const H=m("BLOCKS_API_KEY");if(!H)throw new Error("BLOCKS_API_KEY is required. Run 'blocks login --write-env' to set up credentials.");let G;const Y=t.baseUrl??v?.api.baseUrl;let W,Z;if(H&&Y&&(G=new M(H,Y),L("info","AgentAuth created — will authenticate via connect",{event:"agent_auth_created"})),v){const e=t.baseUrl??v.api.baseUrl,s=await d(a,{baseUrl:e,apiKey:H});if(!s)throw new Error(`[AgentInstance] Agent "${a}" not found in registry. Register the agent first — \`blocks register\` (private + free, recommended) or \`blocks publish\` (public/paid) — before starting an instance.`);if(!s.billingMode)throw new Error(`[AgentInstance] Agent "${a}" registry entry is missing billingMode. Re-register the agent so the registry persists an explicit billingMode.`);W=s.billingMode,Z=s.listing,N="paid"===W?"network":"playground",L("info",`registry billingMode: ${W} — using ${N} environment`,{event:"registry_billing_mode_resolved",billingMode:W,environment:N}),z=N}const J=W??"free",Q=[];let X=null;const ee=p("diagnostics"),te=()=>{let e=!1;return{status:t=>{const a=h(t),s=b.has(a);s&&!e?(e=!0,L("warn",`connectivity degraded: ${a}`,{event:"transport_degraded",category:a,instanceId:c})):!s&&e&&w.has(a)&&(e=!1,L("info","connectivity restored",{event:"transport_restored",category:a,instanceId:c}))}}},ae=(e,t)=>{if(!ee)return;const a={label:e,pn:t,listener:void 0,lastStatusAt:null,lastConnectedAt:null,lastMessageAt:null,lastCategory:null,lastOperation:null,lastStatusCode:null};a.listener=(e=>({status:t=>{const a=t,s=h(a),n=y("string"==typeof a.operation?a.operation:void 0),r="number"==typeof a.statusCode?a.statusCode:null,o=Date.now(),i=e.lastCategory;e.lastStatusAt=o,e.lastCategory=s,e.lastOperation=n,e.lastStatusCode=r,"connected"===s&&(e.lastConnectedAt=o),L("debug","transport status event",{event:"transport_status",client:e.label,category:s,operation:n,statusCode:r,error:Boolean(a.error),affectedChannels:Array.isArray(a.affectedChannels)?a.affectedChannels.length:void 0,instanceId:c}),s&&s!==i&&L("info",`transport status transition [${e.label}]: ${i??"(none)"} -> ${s}`,{event:"transport_status_transition",client:e.label,from:i,to:s,operation:n,statusCode:r,instanceId:c})},message:()=>{e.lastMessageAt=Date.now()}}))(a),Q.push(a),t.addListener(a.listener)},se=e=>{if(!ee)return;const t=Q.findIndex(t=>t.pn===e);if(t<0)return;const a=Q[t];try{e.removeListener(a.listener)}catch{}Q.splice(t,1)};let ne,re=!1;if(t.pubnub)ne=t.pubnub,re=!1,l&&ne.setToken(l);else{const e=f[z];ne=n({publishKey:e.publishKey,subscribeKey:e.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0}),re=!0,l&&ne.setToken(l)}let oe=f[z].subscribeKey,ie=f[z].publishKey;const de=Y?new A({apiKey:H,baseUrl:Y}):void 0,ce=new E({billingMode:J,subscribeKey:oe,publishKey:ie,authProvider:de,baseUrl:Y,createPubNub:()=>{const e=n({publishKey:ie,subscribeKey:oe,userId:`${c}-taskclient`,subscribeRetryUnbounded:!1});return ae("consumer-task",e),e}}),le=t.concurrency??u.concurrency,ue=t.expectedInstances??u.expectedInstances,me=t.maxPendingBacklog,fe=resolveMaxRunningTimeSec(t.maxRunningTimeSec,t.card?.runtime?.maxRunningTimeSec),pe=Date.now();let ke=0;const ge=new T,he=new C,ye=new Map,Ie=new Map,be=new Map,we=new Map,ve=new Map,Se=new Set,Te=new Set,Ce=new Map,$e=new Set,_e=new Map;let Ke,Ee;t.logChannel;const Ae=async()=>{if("function"==typeof ne.setState&&Ee)try{await ne.setState({channels:[Ee],state:{instanceId:c,activeTasks:ke,activeStreams:ge.activeStreamCount,concurrency:le,startedAt:pe,preferredProtocolVersion:R,protocolVersions:[...j]}})}catch{}},xe=(e,t)=>{const a=ye.get(e);a&&(a.delete(t),0===a.size&&ye.delete(e))},Me=async e=>{(e=>{for(const[t,a]of ye)a.has(e)&&(a.delete(e),0===a.size&&ye.delete(t))})(e);const t=ge.releaseAllForTask(e);for(const e of t)if(e.streamClient)try{await e.streamClient.end()}catch{}},Ne=async(e,t)=>{xe(e,t);const a=ge.get(e);if(0===ge.release(e,t)&&a?.streamClient){L("info","last-ref teardown",{event:"stream_registry_last_ref_teardown",streamId:e,affinity:a.affinity,releasingTaskId:t});try{await a.streamClient.end()}catch{}}Ae().catch(()=>{})},Re=async(e,t)=>{const a=ge.forceRemove(e);if((e=>{ye.delete(e)})(e),a){if(a.streamClient)try{await a.streamClient.end()}catch{}for(const e of a.taskIds){const a=he.get(e);if(a){try{const s=f[a.environment??N]??f[N],r=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken),await B(r,e,a.orgId,a.agentName,{type:"terminal",taskId:e,state:"failed",error:t}),r.destroy()}catch{}he.remove(e)}}await Ae()}},je=async(e,t)=>{const a=he.get(e);if(!a)throw new Error(`No cached credentials for task ${e}`);const s=f[a.environment??N]??f[N],r=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken);try{await B(r,e,a.orgId,a.agentName,{...t,type:"terminal",taskId:e})}finally{r.destroy()}await Me(e),he.remove(e),await Ae()},Pe=async(n,r,i,d,c)=>{const u=a,m="pipe"===n.taskKind,f=m?"pipe":"request",p=computeStreamDurationMinutes(n.duration,m,fe);if(m&&"number"==typeof n.durationExpiresAtMs){const e=Math.max(0,n.durationExpiresAtMs-Date.now());if(e>0){const t=setTimeout(()=>{Se.add(n.taskId),c.abort()},e);Ce.set(n.taskId,t)}}await B(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,progress:0,state:"running"});const k=Date.now();_e.set(n.taskId,0);let g=0,h=null,y=null;const I=()=>{if(null===h)return;const e=h;h=null,g=Date.now(),B(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,message:e}).catch(()=>{})},b={taskId:n.taskId,requestParts:n.requestParts??[],reportStatus:e=>{we.set(n.taskId,e);const t=Date.now();if(t-g>=1e3)h=null,g=t,B(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,message:e}).catch(()=>{});else if(h=e,!y){y=setTimeout(()=>{y=null,I()},1e3-(t-g))}},get isCancelled(){return c.signal.aborted},get isExpired(){return Se.has(n.taskId)},hasStream:!!n.hasStream,cancelSignal:c.signal,taskClient:ce,consumerPublicKey:n.consumerPublicKey,downloadInputArtifact:async e=>{const t=e.artifactRef;if(!t)throw new Error(`Request part has no artifactRef (partId: ${e.partId??"none"})`);if("inline"===t.kind)return Buffer.from(s(t));if(!t.channel||!t.fileId||!t.fileName)throw new Error("File artifactRef missing channel, fileId, or fileName");if(!r.downloadFile)throw new Error("PubNub client does not support downloadFile");const a=await r.downloadFile({channel:t.channel,id:t.fileId,name:t.fileName}),n=a?.data;if(Buffer.isBuffer(n))return n;if(n instanceof Uint8Array)return Buffer.from(n);if(n&&"function"==typeof n.arrayBuffer){const e=await n.arrayBuffer();return Buffer.from(e)}throw new Error("Unexpected downloadFile response format")},publishArtifact:async(e,t)=>{const a="string"==typeof e?Buffer.from(e,"utf-8"):e,s=t?.mimeType??"application/octet-stream",o=Y?{baseUrl:Y,authProvider:l?new S(l):void 0,agentAuth:G}:void 0;await F(r,n.taskId,d,u,a,s,t?.fileName,t?.outputId,o)},createStream:async a=>{if(void 0!==a&&("object"!=typeof a||null===a||Array.isArray(a)))throw new TypeError("createStream expects an options object. The positional streamId argument was removed; move any declared-stream key into CreateStreamOptions.declaredStream. See the SDK contract.");if(!n.hasStream)throw new Error("Streaming was not negotiated for this task. Ensure the agent card is registered with streaming capability.");const s=t.card.streams;if(!s||0===Object.keys(s).length)throw new Error("Agent card has no streams block. Streaming requires declared streams in the card.");const i=Object.keys(s);let c;if(a?.declaredStream)c=a.declaredStream;else{if(1!==i.length)throw new Error(`Card declares multiple streams (${i.join(", ")}). Specify declaredStream in CreateStreamOptions.`);c=i[0]}const l=s[c];if(!l)throw new Error(`Stream "${c}" is not declared in the agent card. Available streams: ${i.join(", ")}`);const k=a?.direction??l.direction,g=a?.format??l.format;if(a?.direction&&a.direction!==l.direction)throw new Error(`Direction "${a.direction}" conflicts with card declaration "${l.direction}" for stream "${c}".`);if(a?.format&&a.format!==l.format)throw new Error(`Format "${a.format}" conflicts with card declaration "${l.format}" for stream "${c}".`);const h=a?.external??!1,y=a?.metadata,I=a?.onActivate,b=l.affinity??"dedicated";if("shared"===b&&h)throw new Error(`Shared-affinity external streams are not supported. Declared stream '${c}' has affinity: 'shared' and createStream was called with external: true. Shared affinity requires a single SDK-managed writer with per-task ref-counting; external streams delegate the writer entirely. Use affinity: 'dedicated' with external: true, or affinity: 'shared' without external.`);if(!m){if("shared"===b)throw new Error(`Shared-affinity streams are not supported on request tasks. Declared stream '${c}' has affinity: 'shared'. Request tasks are single-shot; cross-task broadcast is inherently a pipe-task concept. Use affinity: 'dedicated' or remove 'request' from the agent card's taskKinds.`);if("outbound"!==k)throw new Error("Request tasks only support outbound streams");if(h)throw new Error("Request tasks cannot use external streams")}let w;if("shared"===b)w=c;else{const e=(_e.get(n.taskId)??0)+1;_e.set(n.taskId,e),w=`${n.taskId}-${e}`}const v=o.streamChannel(w);if("shared"===b){const e=ye.get(w)?.get(n.taskId);if(e)return e}const{entry:S,isNew:T,isNewForTask:C}=ge.acquire(w,n.taskId,{direction:k,format:g,external:h,affinity:b});he.addStream(n.taskId,w);const E=e=>{if("shared"!==b)return;let t=ye.get(w);t||(t=new Map,ye.set(w,t)),t.set(n.taskId,e)};if(!C){S.setupPromise&&await S.setupPromise;const e=ye.get(w)?.get(n.taskId);if(e)return e;if(S.streamClient){const e=$(w,S.streamClient,n.taskId,{releaseStream:Ne});return E(e),e}throw new Error(`Stream "${w}" same-task reacquire reached an impossible state: setup completed but no client installed. This indicates a logic error in the shared-stream handle cache.`)}if(!T)try{if(S.setupPromise&&await S.setupPromise,"shared"===b&&await(async()=>{await O(r,{taskId:n.taskId,orgId:d,agentName:u,streamId:w,channel:v,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:b,phase:"activate",metadata:y,declaredStream:c})})(),S.streamClient){const e=$(w,S.streamClient,n.taskId,{releaseStream:Ne});return E(e),e}if(h)return _(w,v,"",async()=>{throw new Error("External stream already activated")});throw new Error(`Stream "${w}" exists but has no client`)}catch(e){xe(w,n.taskId);try{await ge.release(w,n.taskId)}catch{}throw e}let A,x;S.setupPromise=new Promise((e,t)=>{A=e,x=t}),S.setupPromise.catch(()=>{});const M=h?"token_request":"embedded";let N;try{N=await O(r,{taskId:n.taskId,orgId:d,agentName:u,streamId:w,channel:v,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:b,phase:M,metadata:y,declaredStream:c})}catch(e){x(e),xe(w,n.taskId);try{await ge.release(w,n.taskId)}catch{}throw e}if(h){A(),S.setupPromise=null;const e=N.token??"";return _(w,v,e,async e=>{await O(r,{taskId:n.taskId,orgId:d,agentName:u,streamId:w,channel:v,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:b,phase:"activate",metadata:e?.metadata??y,declaredStream:c})})}const R=N.token;if(!R){const e=new Error("No T7a token received from stream setup handshake");x(e),xe(w,n.taskId);try{await ge.release(w,n.taskId)}catch{}throw e}const j=new e({subscribeKey:oe,publishKey:ie,token:R,agentName:u,streamId:w,channel:v,format:g,direction:k,affinity:b,gating:m,bundleSizeBytes:a?.bundleSizeBytes,maxLatencyMs:a?.maxLatencyMs});S.streamClient=j;const P=$(w,j,n.taskId,{releaseStream:Ne});if(E(P),A(),S.setupPromise=null,"outbound"===k||"bidirectional"===k){const e=a?.subscribeGraceMs??1e3;e>0&&await new Promise(t=>setTimeout(t,e))}return I&&(S.activated=!0,S.activatePromise=K(w,P,I,Re)),P}};let w;const v=Date.now(),T=D.get(n.taskId);void 0!==T&&(q(n.taskId,{receivedMs:T,runningMs:k,handlerMs:v}),D.delete(n.taskId));try{w=t.handler?await t.handler(n,b):void 0}finally{_e.delete(n.taskId),y&&(clearTimeout(y),y=null),I()}const C=Y?{baseUrl:Y,authProvider:l?new S(l):void 0,agentAuth:G}:void 0;if(w?.artifacts&&w.artifacts.length>0)for(const e of w.artifacts){const t="string"==typeof e.data?Buffer.from(e.data,"utf-8"):e.data;await F(r,n.taskId,d,u,t,e.mimeType,e.fileName,e.outputId,C)}const E=c.signal.aborted;if(m){const e=Se.has(n.taskId),t=Te.has(n.taskId);if(E||e||t){await Me(n.taskId);const a=E&&!e?"canceled":"completed";await B(r,n.taskId,d,u,{type:"terminal",taskId:n.taskId,state:a,...e?{completionReason:"duration_expired"}:{},...t?{reason:"terminated"}:{}}),he.remove(n.taskId),L("info",`Task ${n.taskId} ${a}`,{taskId:n.taskId,agentName:u,owner:i})}else L("info",`Task ${n.taskId} handler returned (pipe, no auto-terminal)`,{taskId:n.taskId,agentName:u,owner:i})}else{const e=Te.has(n.taskId);await Me(n.taskId);const t=E?"canceled":"completed";await B(r,n.taskId,d,u,{type:"terminal",taskId:n.taskId,state:t,...e?{reason:"terminated"}:{}}),he.remove(n.taskId),L("info",`Task ${n.taskId} ${t}`,{taskId:n.taskId,agentName:u,owner:i})}},Ue=(()=>{let e=!1;return(t,a)=>{const s=h(t),n="number"==typeof t.statusCode?t.statusCode:null;if(L("debug","access-denied handler invoked",{event:"access_denied_handler_invoked",category:s,operation:y(a),statusCode:n,alreadyHandled:e,instanceId:c}),I(t)&&!e){e=!0,L("error","access denied — destroying control client (agent will go silent)",{event:"access_denied_destroy",category:s,operation:y(a),statusCode:n,instanceId:c,controlChannel:Ee}),L("error","access token expired or revoked — agent is no longer receiving tasks. Re-register the agent to resume.",{event:"access_denied_user_message",instanceId:c});try{ne.destroy()}catch{}}}})();let qe=te();const De={status:e=>{const t=e,a=String(t.category??""),s=String(t.operation??"");"PNConnectedCategory"===a&&L("debug","control client connected",{event:"control_connected",controlChannel:Ee}),Ue(t,s)},message:e=>{const s=e.message;if(!s)return;if("SwitchEnvironment"===s.type){const e=s,t=e.environment;return void(t&&t!==z&&t in f&&((e,t)=>{if(L("info",`SwitchEnvironment requested: ${z} -> ${e}`,{pamToken:t?"present":"absent"}),!t)return void L("error","SwitchEnvironment rejected: pamToken is required but was absent. The backend must include a pamToken in SwitchEnvironment messages.");const a=ne,s=re;try{a.removeListener(De),a.removeListener(qe),Ee&&a.unsubscribe({channels:[Ee]})}catch{}se(a);const r=f[e];if(ne=n({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0}),re=!0,Ke=void 0,ne.setToken(t),0!==ue&&"function"==typeof ne.setFilterExpression&&ne.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),qe=te(),ne.addListener(De),ne.addListener(qe),ae("control",ne),Ee&&ne.subscribe({channels:[Ee]}),s)try{a.destroy()}catch{}z=e,oe=r.subscribeKey,ie=r.publishKey,ce.updateKeys(r.subscribeKey,r.publishKey),L("info",`Switched to ${e} environment`),Ae().catch(()=>{})})(t,e.pamToken))}(async(e,s)=>{if("StartTask"===e.type){U()&&D.set(e.taskId,Date.now());const r=z,o=((e,t)=>{if("string"==typeof e&&e.length>0)return e;const a=t?.sub;return"string"==typeof a&&a.length>0?a:"anonymous"})(e.ownerId,e.callerClaims),i=e.orgId??o;if($e.has(e.taskId))return void L("warn",`Ignoring duplicate StartTask for in-flight task ${e.taskId}`,{taskId:e.taskId});const d=Boolean(s?.broadcast);if(e.protocolVersion&&!P(e.protocolVersion))return d?void L("debug",`Ignoring broadcast StartTask ${e.taskId} with unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId}):(await B(ne,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"unsupported_protocol_version"},e.protocolVersion),void L("warn",`Task ${e.taskId} rejected: unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId,instanceId:c}));if("pipe"===(e.taskKind||"request")){const t=e.duration,s=e.durationExpiresAtMs;if(null==t||!Number.isInteger(t)||t<1||t>43200||"number"!=typeof s||s<=0)return L("error",`Task ${e.taskId} rejected: pipe StartTask with invalid duration (duration=${t}, durationExpiresAtMs=${s})`,{taskId:e.taskId,instanceId:c}),void await B(ne,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"invalid_start_task"},e.protocolVersion)}const l=ke+ge.activeStreamCount;if(le>0&&l>=le)return d?void L("debug",`Task ${e.taskId} skipped (broadcast, at capacity ${l}/${le})`,{taskId:e.taskId,instanceId:c}):(await B(ne,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"agent_at_capacity"}),void L("warn",`Task ${e.taskId} rejected: agent at capacity`,{taskId:e.taskId,instanceId:c,activeTasks:ke}));Ie.set(e.taskId,o),be.set(e.taskId,i),$e.add(e.taskId),ke++,e.controlToken&&(Ke=e.controlToken);const u=new AbortController;ve.set(e.taskId,u);let m=null;try{const s=f[r];if(!s.publishKey||!s.subscribeKey)throw new Error("Per-task PubNub client requires valid pub/sub keys. When using an injected PubNub client, ensure it was created with publishKey and subscribeKey.");m=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1}),e.writeToken&&m.setToken(e.writeToken),ae(`task-sub:${e.taskId}`,m),he.set(e.taskId,{ownerId:o,orgId:i,writeToken:e.writeToken??"",agentName:a,environment:r}),await Ae();const{writeToken:d,controlToken:l,...p}=e;t.onStartTask?await t.onStartTask(e,m):await Pe(p,m,o,i,u)}catch(s){if(V(s,"task-handler"))return;const n=s?.message??"Agent instance error",r=m??ne;try{await Me(e.taskId)}catch{}await B(r,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:n}),t.onError&&await t.onError(e.taskId,s),L("error",n,{taskId:e.taskId}),he.remove(e.taskId)}finally{const t=Ce.get(e.taskId);if(t&&(clearTimeout(t),Ce.delete(e.taskId)),$e.delete(e.taskId),Ie.delete(e.taskId),be.delete(e.taskId),we.delete(e.taskId),ve.delete(e.taskId),Se.delete(e.taskId),Te.delete(e.taskId),m){se(m);try{m.destroy()}catch{}}ke--,0===ke&&Ke&&ne.setToken(Ke),await Ae()}}else if("CancelTask"===e.type){const t=ve.get(e.taskId);t?(t.abort(),L("info",`Task ${e.taskId} cancel requested (cooperative)`,{taskId:e.taskId,instanceId:c})):he.get(e.taskId)&&await je(e.taskId,{state:"canceled"})}else if("ExpireTask"===e.type)ve.has(e.taskId)?(Se.add(e.taskId),ve.get(e.taskId)?.abort(),L("info",`Task ${e.taskId} expired`,{taskId:e.taskId,instanceId:c})):he.get(e.taskId)&&await je(e.taskId,{state:"completed",completionReason:"duration_expired"});else if("TerminateTask"===e.type)ve.has(e.taskId)?(Te.add(e.taskId),ve.get(e.taskId)?.abort()):he.get(e.taskId)&&await je(e.taskId,{state:"canceled",reason:"terminated"});else if("PauseTask"===e.type){if($e.has(e.taskId)){const t=be.get(e.taskId)??"anonymous";await B(ne,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"paused"})}}else if("ResumeTask"===e.type){if($e.has(e.taskId)){const t=be.get(e.taskId)??"anonymous";await B(ne,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"resumed"})}}else"RetryTask"===e.type&&L("info","RetryTask received (no-op)",{taskId:e.taskId})})(s,e.userMetadata&&"object"==typeof e.userMetadata?e.userMetadata:void 0).catch(e=>{V(e,"control-message")||L("error","unhandled error in message handler",{event:"message_handler_error",error:e instanceof Error?e.message:String(e),stack:e instanceof Error?e.stack:void 0})})}};ne.addListener(De),ne.addListener(qe),ae("control",ne),ee&&null===X&&(X=setInterval(()=>{const e=Date.now();let t=!1;const a=Q.map(a=>{const s=null!==a.lastStatusAt?e-a.lastStatusAt:null,n=_isDiagEntryStale({lastStatusAt:a.lastStatusAt,lastCategory:a.lastCategory,now:e,thresholdMs:6e4});n&&(t=!0);const r="function"==typeof a.pn.getSubscribedChannels?a.pn.getSubscribedChannels().length:null;return{client:a.label,lastCategory:a.lastCategory,lastOperation:a.lastOperation,lastStatusCode:a.lastStatusCode,msSinceStatus:s,msSinceConnected:null!==a.lastConnectedAt?e-a.lastConnectedAt:null,msSinceMessage:null!==a.lastMessageAt?e-a.lastMessageAt:null,subscribedChannels:r,stale:n}}),s={event:"transport_alive_snapshot",instanceId:c,clients:a};t?L("info","transport alive snapshot — STALE clients present",s):L("debug","transport alive snapshot",s)},1e4),"function"==typeof X.unref&&X.unref()),ee&&L("info","transport diagnostics armed",{event:"transport_diagnostics_armed",snapshotIntervalMs:1e4,staleThresholdMs:6e4,instanceId:c}),i(a,{instanceId:c,billingMode:J,description:t.description,scaling:{expectedInstances:ue,concurrency:le,maxPendingBacklog:me,maxRunningTimeSec:fe},card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:Z??t.listing,actor:`agent-instance:${c}`,baseUrl:t.baseUrl??v?.api.baseUrl,agentAuth:G}).then(e=>{if(L("info",`registered agent: ${a} (instance: ${c})`,{event:"agent_registered",agentName:a,instanceId:c}),!e.controlChannel)throw new Error("Connect response missing controlChannel — server may be outdated");Ee=e.controlChannel,e.pamToken&&ne.setToken(e.pamToken),0!==ue&&"function"==typeof ne.setFilterExpression&&ne.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),ne.subscribe({channels:[Ee]}),Ae().catch(()=>{})}).catch(e=>{const t=e instanceof Error?e.message:String(e);L("error",`failed to register agent: ${a} — ${t}`,{event:"agent_registration_failed",agentName:a,instanceId:c,error:t}),V(e,"connect")}),L("info",`Agent instance ${c} started (agent name: ${a})`,{agentName:a,event:"agent_instance_started",instanceId:c});return{stop:()=>{try{null!==X&&(clearInterval(X),X=null);for(const e of Q)try{e.pn.removeListener(e.listener)}catch{}Q.length=0,ee&&L("info","transport diagnostics disarmed",{event:"transport_diagnostics_disarmed",instanceId:c}),de?.destroy(),ce.destroy(),ne.removeListener(De),ne.removeListener(qe),Ee&&ne.unsubscribe({channels:[Ee]}),re&&ne.destroy();for(const e of ge.streamIds()){const t=ge.get(e);if(t?.streamClient)try{t.streamClient.end()}catch{}}ge.clear(),ye.clear(),he.clear()}catch{}},agentName:a,instanceId:c,get controlChannel(){return Ee},get pubnub(){return ne},get subscribeKey(){return f[z].subscribeKey},taskClient:ce,publishTerminal:je,failStream:Re,cdmConfig:v}};export const artifactUtils={buildArtifactRef:t,shouldInlineArtifact:a};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Shared pre-signed URL upload helper for the task artifacts initiative.
2
+ * Shared pre-signed URL upload helper for task artifacts.
3
3
  *
4
4
  * Implements the three-step upload handshake:
5
5
  * 1. request-upload -> Backend returns uploadUrl, formFields, uploadId
@@ -25,8 +25,7 @@ export interface PubNubClientConfig {
25
25
  * Default: true (parity with the Python SDK). Per-task / ephemeral /
26
26
  * per-stream call sites MUST pass `subscribeRetryUnbounded: false`
27
27
  * explicitly so they keep the fail-fast retry budget — a stuck task
28
- * is preferable to a stuck loop. See dev_docs/SDK_CONTRACT.md
29
- * §Cross-SDK retry-budget defaults for the contract.
28
+ * is preferable to a stuck loop.
30
29
  */
31
30
  subscribeRetryUnbounded?: boolean;
32
31
  }
@@ -45,7 +45,7 @@ export declare class RpcError extends Error {
45
45
  /**
46
46
  * Typed error for backend `BillingModeMismatch` responses.
47
47
  *
48
- * Backend wire shape (per Phase 1 `bmc-data` IMPL_REPORT):
48
+ * Backend wire shape (per Phase 1 `bmc-data` report):
49
49
  *
50
50
  * {
51
51
  * "jsonrpc": "2.0",
@@ -15,7 +15,7 @@
15
15
  * cache entry, releases the registry taskId, and — only when the last
16
16
  * ref-holder releases — tears down the underlying StreamClient. On shared
17
17
  * streams the teardown never publishes a `stream_end` marker (see
18
- * SDK_CONTRACT §8.4.1a shared-affinity carve-out).
18
+ * the shared-affinity carve-out).
19
19
  */
20
20
  import type { StreamClient, InboundMessage, StreamError } from '../stream/index.js';
21
21
  /** Hook the agent-instance passes to the stream wrapper so `end()` can
@@ -28,7 +28,7 @@ export interface AgentInstanceHooks {
28
28
  *
29
29
  * The read/error/uuid surface mirrors the underlying `StreamClient` so
30
30
  * handlers and consumers see the same API. `onInboundDone` is intentionally
31
- * NOT forwarded — it's classified internal-only by SDK_CONTRACT §8.3.8.
31
+ * NOT forwarded — it's classified internal-only by the SDK contract
32
32
  * Handlers needing a "stream drained" signal should `await` the
33
33
  * for-await-of loop on `bytes()` / `events()` / `inbound`.
34
34
  */
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * `acquire()` is idempotent within a task: a second call with the same
18
18
  * `(streamId, taskId)` returns `{ isNew: false, isNewForTask: false }`
19
- * and does not grow the set. See SHARED_STREAM_LIFECYCLE_IMPL §Fix (e).
19
+ * and does not grow the set. See the shared-stream lifecycle work, Fix (e).
20
20
  */
21
21
  import type { StreamClient } from '../stream/index.js';
22
22
  import type { StreamAffinity } from '../stream/descriptor.js';
@@ -47,7 +47,7 @@ export interface StreamRegistryEntry {
47
47
  /**
48
48
  * Derived reference count. Kept as a getter so existing callers /
49
49
  * tests that inspect `entry.refCount` continue to work after the
50
- * shape change (see SHARED_STREAM_LIFECYCLE_IMPL §Risk "Registry
50
+ * shape change (see the shared-stream lifecycle Risk note "Registry
51
51
  * shape change ripples").
52
52
  */
53
53
  readonly refCount: number;
@@ -75,7 +75,7 @@ export declare class StreamRegistry {
75
75
  /**
76
76
  * Get or create a registry entry for a stream.
77
77
  *
78
- * Three-case matrix (per SHARED_STREAM_LIFECYCLE_IMPL §Fix (e)):
78
+ * Three-case matrix (per the shared-stream lifecycle Fix (e)):
79
79
  * 1. Entry doesn't exist -> create, taskIds = {taskId}, isNew: true, isNewForTask: true
80
80
  * 2. Entry exists + taskId already tracked -> idempotent no-op, isNew: false, isNewForTask: false
81
81
  * 3. Entry exists + taskId is new -> add to set, isNew: false, isNewForTask: true
@@ -113,8 +113,7 @@ export declare class StreamRegistry {
113
113
  * implementation silently broke that fan-out by clearing the set
114
114
  * before returning. The single reader lives at
115
115
  * `agent-instance.ts#failStreamImpl` — verify its loop still works
116
- * before changing this behavior. See QUESTIONS.md R6
117
- * (shared_stream_lifecycle).
116
+ * before changing this behavior.
118
117
  */
119
118
  forceRemove(streamId: string): StreamRegistryEntry | undefined;
120
119
  /**
@@ -46,7 +46,7 @@ export interface TerminalEvent {
46
46
  * audit). Fires zero or once per session: suppressed once a terminal has
47
47
  * been delivered (causality), and suppressed on duplicate wire emissions
48
48
  * of the event itself (e.g. PubNub cache replay before timetoken-dedup
49
- * catches it). See `schemas/SDK/task-events/cancel_requested.schema.json`.
49
+ * catches it).
50
50
  */
51
51
  export interface CancelRequestedEvent {
52
52
  type: 'cancel_requested';
@@ -179,7 +179,7 @@ export declare class TaskSession {
179
179
  onArtifact(cb: (event: ArtifactEvent) => void): Unsubscribe;
180
180
  onTerminal(cb: (event: TerminalEvent) => void): Unsubscribe;
181
181
  /**
182
- * BLOCKS-370: subscribe to backend-published `cancel_requested` events.
182
+ * Subscribe to backend-published `cancel_requested` events.
183
183
  * Fires zero or once per session — suppressed once a terminal has been
184
184
  * delivered (causality) and suppressed on duplicate wire emissions of
185
185
  * the event itself. Carries `{ taskId, ts }`. No actor identity (the
@@ -2,7 +2,7 @@ import type { TerminalEvent } from './task-session.js';
2
2
  /**
3
3
  * Layer-1 dedup for terminal events.
4
4
  *
5
- * Cooperative cancel and the scanner's Phase-6 safety net can both produce
5
+ * Cooperative cancel and the scanner's safety net can both produce
6
6
  * a terminal for the same task. The DB layer (`taskFanout` PubNub Function)
7
7
  * already dedupes at the row-status level, but consumers subscribed to the
8
8
  * task channel still see two wire-level terminals. This tracker enforces
@@ -49,7 +49,7 @@ export interface StreamClientOptions {
49
49
  /**
50
50
  * Stream affinity — gates the `stream_end` publish on shared channels.
51
51
  * Shared streams never publish the end marker from either producer or
52
- * consumer-writer paths (SDK_CONTRACT §8.4.1a shared-affinity carve-out).
52
+ * consumer-writer paths (shared-affinity carve-out).
53
53
  * Default: 'dedicated'.
54
54
  */
55
55
  affinity?: StreamAffinity;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocks-network/sdk",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "type": "module",
5
5
  "description": "Blocks Network SDK",
6
6
  "exports": {