@blocks-network/sdk 0.1.61 → 0.1.63

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
@@ -34,7 +34,7 @@ Create an `agent-card.json`:
34
34
  "provider": { "organization": "Your Org" }
35
35
  },
36
36
  "capabilities": { "taskKinds": ["request"] },
37
- "skills": [{ "id": "main", "name": "Main Skill" }],
37
+ "tags": [{ "id": "main", "name": "Main" }],
38
38
  "runtime": {
39
39
  "handler": "./handler.ts"
40
40
  }
@@ -77,7 +77,7 @@ The agent card follows the A2A specification with a `runtime` extension:
77
77
  "provider": { "organization": "Your Org" }
78
78
  },
79
79
  "capabilities": { "taskKinds": ["request"] },
80
- "skills": [{ "id": "main", "name": "Main Skill" }],
80
+ "tags": [{ "id": "main", "name": "Main" }],
81
81
  "runtime": {
82
82
  "handler": "./handler.ts",
83
83
  "handlerExport": "default",
@@ -89,29 +89,25 @@ The agent card follows the A2A specification with a `runtime` extension:
89
89
  Note: `identity.agentName` must use only alphanumeric characters and
90
90
  underscores (no hyphens). The pattern is `^[a-zA-Z0-9_]+$`.
91
91
 
92
- ## PubNub Subscribe Strategy
93
-
94
- The Node SDK uses PubNub Event Engine (`enableEventEngine: true`) on all
95
- subscribing PubNub clients (control client, per-task client, and per-stream
96
- client). Event Engine replaces the legacy subscribe manager with a
97
- deterministic state machine for subscribe, reconnect, and retry. The SDK
98
- does not set `autoNetworkDetection` or `restore` (both are browser-only
99
- settings).
100
-
101
- **Control client** (long-lived, drives agent online presence) is
102
- constructed with `subscribeRetryUnbounded: true`, which configures an
103
- `ExponentialRetryPolicy` with `maximumRetry: 43_200` (~30 days at the
104
- 60s cap). This prevents the Event Engine from parking in
105
- `RECEIVE_FAILED`/`HEARTBEAT_FAILED` after the default 6-attempt budget
106
- is exhausted, so the agent automatically resumes within seconds of a
107
- network outage ending. Transport-level retry activity is forwarded to
108
- the SDK log via the `onRetry` hook, surfacing as
109
- `event: 'pubnub_transport_retry'` warn lines so the agent's log stays
110
- active during outages instead of going silent.
111
-
112
- **Per-task and per-stream clients** keep PubNub's default 6-attempt
113
- retry budget. They are short-lived; a stuck task should fail cleanly
114
- rather than loop forever.
92
+ ## Subscribe Strategy
93
+
94
+ The SDK keeps the control client subscribed for the agent's lifetime
95
+ and configures the underlying realtime transport to retry indefinitely
96
+ on transient failures. The agent therefore resumes within seconds of a
97
+ network outage ending without any manual intervention.
98
+
99
+ Connectivity-state changes are surfaced through the SDK log:
100
+
101
+ - `event: 'transport_degraded'` (warn) — emitted when the transport
102
+ reports network down, network issues, timeout, or a malformed
103
+ response. The log line reads `connectivity degraded: <category>`,
104
+ where `<category>` is one of `network_down`, `network_issues`,
105
+ `timeout`, `malformed_response`.
106
+ - `event: 'transport_restored'` (info) — emitted on reconnection.
107
+
108
+ Per-task and per-stream clients keep the default short retry budget.
109
+ They are short-lived; a stuck task should fail cleanly rather than
110
+ loop forever.
115
111
 
116
112
  ## API
117
113
 
@@ -128,9 +124,19 @@ Go CLI's `blocks login` command writes the API key to `.env` when
128
124
  invoked with `--write-env` (or by answering "y" to its interactive
129
125
  prompt).
130
126
 
131
- PAM tokens for PubNub channel access are managed by the SDK at runtime
127
+ Access tokens for realtime channel access are managed by the SDK at runtime
132
128
  (granted at registration, refreshed per-task on the control channel).
133
- No CLI involvement is needed for PAM.
129
+ No CLI involvement is needed for token management.
130
+
131
+ ### Logging
132
+
133
+ The SDK respects two log-related environment variables:
134
+
135
+ - `LOG_LEVEL` (default: `info`) — `error` | `warn` | `info` | `debug`. Threshold for the SDK's own log lines (`[AgentInstance]`, `[Transport]`).
136
+ - `BLOCKS_DEBUG_INTERNAL` (default: unset) — comma-separated list of subsystems to enable. Neither subsystem is implied by `LOG_LEVEL=debug`.
137
+ - `diagnostics` — Blocks-authored transport-connectivity diagnostics (connection-state transitions, alive snapshots).
138
+ - `forward_transport` — routes the underlying transport's own log output through the Blocks logger with a `[Transport]` prefix, filtered by `LOG_LEVEL`. Off by default because the forwarded messages reference internal SDK config concepts that aren't meaningful to a Blocks developer. **Security note:** forwarded entries may include constructed transport URLs whose query strings carry the access-token (`auth=…`) or request-signature (`signature=…`) parameters. Do not enable in environments that ship logs to less-trusted sinks (third-party log shippers, public Sentry projects, support-ticket attachments).
139
+ - Example: `BLOCKS_DEBUG_INTERNAL=diagnostics,forward_transport`
134
140
 
135
141
  ## Examples
136
142
 
@@ -387,7 +393,7 @@ extends `Uint8Array`.
387
393
 
388
394
  ### Stream Consumer APIs
389
395
 
390
- All consumer iterators (`bytes()`, `events()`, `readable()`, `inbound`) deliver messages in sequence order. The SDK's reorder buffer transparently corrects out-of-order PubNub delivery and drops duplicate messages. To customize or disable reordering, pass `reorderTimeoutMs` to `open()`:
396
+ All consumer iterators (`bytes()`, `events()`, `readable()`, `inbound`) deliver messages in sequence order. The SDK's reorder buffer transparently corrects out-of-order delivery and drops duplicate messages. To customize or disable reordering, pass `reorderTimeoutMs` to `open()`:
391
397
 
392
398
  ```typescript
393
399
  // Default: reorder buffer with 750ms gap timeout
@@ -427,10 +433,10 @@ readable.pipe(createWriteStream('./output.bin'));
427
433
  ### Handling Stream Errors
428
434
 
429
435
  Every `StreamClient` exposes an `onError(cb)` registration method.
430
- The callback fires whenever the stream's PubNub subscribe loop
431
- surfaces an error-category status event: PAM revocation, network
432
- issues, timeouts, or any other category the PubNub SDK marks as an
433
- error. The payload is a typed `StreamError`:
436
+ The callback fires whenever the stream's subscribe loop surfaces an
437
+ error-category status event: access revocation, network issues,
438
+ timeouts, or any other transport error category. The payload is a
439
+ typed `StreamError`:
434
440
 
435
441
  ```typescript
436
442
  import type { StreamError } from '@blocks-network/sdk/stream';
@@ -450,14 +456,15 @@ the stream so `for await` / `for msg in ...` loops exit cleanly
450
456
  instead of hanging waiting for a `stream_end` that will never
451
457
  arrive:
452
458
 
453
- - `PNAccessDeniedCategory` — PAM revocation (admin-terminate,
459
+ - `access_denied` — access grant revoked (admin-terminate or
454
460
  token denied). This is the signal that the server-side grant is
455
- gone even if the cached T7c's `exp` claim has not elapsed.
456
- - `PNBadRequestCategory` — auth configuration or malformed grant.
461
+ gone even if the cached token's `exp` claim has not elapsed.
462
+ - `bad_request` — auth configuration or malformed grant.
457
463
 
458
- All other error categories (network transients, timeouts, etc.)
459
- fire `onError` with `fatal: false` and leave the stream running so
460
- PubNub's built-in retry machinery can recover.
464
+ All other error categories (`network_down`, `network_issues`,
465
+ `timeout`, `malformed_response`, `other`) fire `onError` with
466
+ `fatal: false` and leave the stream running so the transport's
467
+ built-in retry machinery can recover.
461
468
 
462
469
  ### Opening Task Streams
463
470
 
@@ -519,8 +526,8 @@ The option is supported on both `sendMessage()` and `connect()`.
519
526
 
520
527
  ### Reconnecting to Terminal Tasks
521
528
 
522
- Stream data is **live-only** — PubNub does not persist stream
523
- payloads. When `client.connect({ taskId })` returns a session for a
529
+ Stream data is **live-only** — the realtime transport does not persist
530
+ stream payloads. When `client.connect({ taskId })` returns a session for a
524
531
  task that has already finished, a stream that was **never opened
525
532
  while the task was active** throws a typed `StreamUnavailableError`
526
533
  from `StreamRef.open()` instead of subscribing to a dead channel.
@@ -601,8 +608,8 @@ are browser-safe — no Node.js `Buffer` polyfill needed:
601
608
  `Blob` field. `fetch` computes the multipart boundary
602
609
  automatically — no manual `Content-Type: multipart/form-data`
603
610
  header, no `Buffer.concat`.
604
- - `downloadArtifact()` handles the three PubNub v10 download shapes
605
- (raw `Uint8Array`, `Blob`, and legacy `PubNubFile` with
611
+ - `downloadArtifact()` handles the three download shapes returned by
612
+ the transport (raw `Uint8Array`, `Blob`, and legacy file object with
606
613
  `toArrayBuffer()`) with typeof-guarded branches — no
607
614
  `Buffer.isBuffer` OR-order short-circuits.
608
615
  - `decodeInlineArtifact()` uses `atob` + `Uint8Array` and
@@ -643,4 +650,4 @@ the SDK does not read from `process.env` in browser environments:
643
650
 
644
651
  ## License
645
652
 
646
- PubNub
653
+ See [LICENSE](./LICENSE).
package/dist/index.d.ts CHANGED
@@ -2,13 +2,13 @@ export { startAgentInstance } from './runtime/agent-instance.js';
2
2
  export type { ArtifactEntry, HandlerFn, HandlerResult, RequestPart, StartTaskMessage, ExpireTaskMessage, TaskContext, CreateStreamOptions, AgentInstanceOptions, AgentInstancePresenceState, AgentInstanceHandle, } from './runtime/agent-instance.js';
3
3
  export type { StreamObject, OnActivateCallback } from './runtime/stream-context.js';
4
4
  export type { InboundMessage, StreamError } from './stream/index.js';
5
- export type { AgentCard, OutputAgentCard, AgentSkill } from './runtime/agent-registry.js';
5
+ export type { AgentCard, OutputAgentCard, AgentTag } from './runtime/agent-registry.js';
6
6
  export { createPubNubClient, type PubNubClientConfig } from './runtime/pubnub-client.js';
7
7
  export { createChannelManager, ChannelManager, streamChannel } from './runtime/channel-manager.js';
8
8
  export { buildArtifactRef, shouldInlineArtifact, decodeInlineArtifact, downloadArtifact, type ArtifactRef, type DownloadedArtifact, } from './runtime/artifacts.js';
9
- export { connectAgent, fetchAgentRegistry, getAgent, removeAgent, fetchAgentsBySkill, fetchAgentsByListing, type AgentRegistryResult, } from './runtime/agent-registry.js';
9
+ export { connectAgent, fetchAgentRegistry, getAgent, removeAgent, fetchAgentsByTag, fetchAgentsByListing, type AgentEntry, type AgentRegistryResult, } from './runtime/agent-registry.js';
10
10
  export { DEFAULTS } from './defaults.js';
11
- /** Platform-wide upload ceiling (bytes). Mirrors afui_mvp_backend MAX_FILE_SIZE_BYTES. */
11
+ /** Platform-wide upload ceiling (bytes). Mirrors the service's MAX_FILE_SIZE_BYTES. */
12
12
  export declare const BLOCKS_MAX_UPLOAD_BYTES: number;
13
13
  export { loadBlocksConfig, type BlocksConfig } from './config-loader.js';
14
14
  export { TaskClient, AnonTaskAccessDeniedError } from './runtime/task-client.js';
@@ -22,7 +22,7 @@ export type { StreamDescriptor } from './runtime/stream-ref.js';
22
22
  export { AgentAuth, AgentAuthFatalError } from './runtime/agent-auth.js';
23
23
  export type { RegistrationPayload, RegistrationResult } from './runtime/agent-auth.js';
24
24
  export type { AuthProvider } from './runtime/auth-provider.js';
25
- export { ConsumerAuth } from './runtime/consumer-auth.js';
25
+ export { ConsumerAuth, AuthRefreshFailedError } from './runtime/consumer-auth.js';
26
26
  export type { ConsumerAuthOptions, TokenEndpointConfig, TokenEndpointCredentials, TokenResult, } from './runtime/consumer-auth.js';
27
27
  export { StreamRegistry } from './runtime/stream-registry.js';
28
28
  export { CredentialCache } from './runtime/credential-cache.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{startAgentInstance}from"./runtime/agent-instance.js";export{createPubNubClient}from"./runtime/pubnub-client.js";export{createChannelManager,ChannelManager,streamChannel}from"./runtime/channel-manager.js";export{buildArtifactRef,shouldInlineArtifact,decodeInlineArtifact,downloadArtifact}from"./runtime/artifacts.js";export{connectAgent,fetchAgentRegistry,getAgent,removeAgent,fetchAgentsBySkill,fetchAgentsByListing}from"./runtime/agent-registry.js";export{DEFAULTS}from"./defaults.js";import{DEFAULTS as e}from"./defaults.js";export const BLOCKS_MAX_UPLOAD_BYTES=e.maxUploadBytes;export{loadBlocksConfig}from"./config-loader.js";export{TaskClient,AnonTaskAccessDeniedError}from"./runtime/task-client.js";export{RpcError,BillingModeMismatchError}from"./runtime/rpc-client.js";export{TaskSession}from"./runtime/task-session.js";export{textPart,filePart,filePartFromPath}from"./runtime/part-helpers.js";export{StreamRef,StreamUnavailableError}from"./runtime/stream-ref.js";export{AgentAuth,AgentAuthFatalError}from"./runtime/agent-auth.js";export{ConsumerAuth}from"./runtime/consumer-auth.js";export{StreamRegistry}from"./runtime/stream-registry.js";export{CredentialCache}from"./runtime/credential-cache.js";export{fetchCdmConfig,DEFAULT_CDM_URL}from"./runtime/cdm-config.js";
1
+ export{startAgentInstance}from"./runtime/agent-instance.js";export{createPubNubClient}from"./runtime/pubnub-client.js";export{createChannelManager,ChannelManager,streamChannel}from"./runtime/channel-manager.js";export{buildArtifactRef,shouldInlineArtifact,decodeInlineArtifact,downloadArtifact}from"./runtime/artifacts.js";export{connectAgent,fetchAgentRegistry,getAgent,removeAgent,fetchAgentsByTag,fetchAgentsByListing}from"./runtime/agent-registry.js";export{DEFAULTS}from"./defaults.js";import{DEFAULTS as e}from"./defaults.js";export const BLOCKS_MAX_UPLOAD_BYTES=e.maxUploadBytes;export{loadBlocksConfig}from"./config-loader.js";export{TaskClient,AnonTaskAccessDeniedError}from"./runtime/task-client.js";export{RpcError,BillingModeMismatchError}from"./runtime/rpc-client.js";export{TaskSession}from"./runtime/task-session.js";export{textPart,filePart,filePartFromPath}from"./runtime/part-helpers.js";export{StreamRef,StreamUnavailableError}from"./runtime/stream-ref.js";export{AgentAuth,AgentAuthFatalError}from"./runtime/agent-auth.js";export{ConsumerAuth,AuthRefreshFailedError}from"./runtime/consumer-auth.js";export{StreamRegistry}from"./runtime/stream-registry.js";export{CredentialCache}from"./runtime/credential-cache.js";export{fetchCdmConfig,DEFAULT_CDM_URL}from"./runtime/cdm-config.js";
@@ -192,7 +192,7 @@ export interface ControlMessage {
192
192
  /**
193
193
  * True when the diag entry is "parked" — past the staleness threshold
194
194
  * AND not currently in the connected state. A long-silent
195
- * PNConnectedCategory entry is healthy by definition: at LOG_LEVEL=info
195
+ * 'connected' entry is healthy by definition: at LOG_LEVEL=info
196
196
  * the SDK suppresses successful-heartbeat status events
197
197
  * (announceSuccessfulHeartbeats=false), so lastStatusAt does not
198
198
  * advance on a healthy idle client. Without the category gate, the
@@ -1 +1 @@
1
- import{StreamClient as e}from"../stream/index.js";import{buildArtifactRef as t,shouldInlineArtifact as a,decodeInlineArtifact as n}from"./artifacts.js";import{createPubNubClient as s}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,isInternalDebugEnabled as p,_resolveLogLevel as k,_LOG_LEVEL_ORDER as g}from"./logger.js";import{uploadFile as y}from"./file-upload.js";import{StaticAuthProvider as h}from"./auth-provider.js";import{StreamRegistry as b}from"./stream-registry.js";import{CredentialCache as I}from"./credential-cache.js";import{createStreamObject as w,createExternalStreamObject as v,runOnActivate as S}from"./stream-context.js";import{TaskClient as T}from"./task-client.js";import{ConsumerAuth as C}from"./consumer-auth.js";import{fetchCdmConfig as $}from"./cdm-config.js";import{AgentAuth as K,AgentAuthFatalError as _}from"./agent-auth.js";import{CURRENT_PROTOCOL_VERSION as A,SUPPORTED_PROTOCOL_VERSIONS as E,isProtocolVersionSupported as x}from"./protocol-version.js";const M=async(e,t,a,n,s,i=A)=>{const d=r(n);try{await e.publish({channel:d.taskChannel(t,a),message:o({...s,protocolVersion:i}),storeInHistory:!0,sendByPost:!0,meta:o({agentName:n,taskId:t,protocolVersion:i})})}catch{}};export function _isDiagEntryStale(e){const{lastStatusAt:t,lastCategory:a,now:n,thresholdMs:s}=e;return null!==t&&("PNConnectedCategory"!==a&&n-t>s)}const N=(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 R=async(e,n,s,r,o,i,d,c,l)=>{const u={type:"artifact",taskId:n};if(c&&(u.outputId=c),a(o.length))await M(e,n,s,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 y(l,{role:"provider-output",taskId:n,fileName:d??`${n}-artifact`,fileSize:o.length,mimeType:i,outputId:c},o)}};async function P(e,t){const a=`setup.${t.orgId}.${t.taskId}`,n={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:A};t.phase&&(n.phase=t.phase),t.metadata&&(n.metadata=t.metadata);try{throw await e.publish({channel:a,message:o(n),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,y=null,j="playground",U=j;if(t.pubnub){const e=t.pubnub._configuration?.keySet,a={publishKey:e?.publishKey??"",subscribeKey:e?.subscribeKey??""};f={playground:a,network:a}}else y=await $(t.cdmUrl),f={playground:y.playground,network:y.network},N("info","CDM config loaded — environment switching enabled",{event:"cdm_config_loaded"});const q=m("BLOCKS_API_KEY");if(!q)throw new Error("BLOCKS_API_KEY is required. Run 'blocks login --write-env' to set up credentials.");let D;const B=t.baseUrl??y?.api.baseUrl;let V,F;if(q&&B&&(D=new K(q,B),N("info","AgentAuth created — will authenticate via connect",{event:"agent_auth_created"})),y){const e=t.baseUrl??y.api.baseUrl,n=await d(a,{baseUrl:e,apiKey:q});if(!n)throw new Error(`[AgentInstance] Agent "${a}" not found in registry. Register the agent (e.g. via \`blocks publish\`) before starting an instance.`);if(!n.billingMode)throw new Error(`[AgentInstance] Agent "${a}" registry entry is missing billingMode. Re-register the agent so the registry persists an explicit billingMode.`);V=n.billingMode,F=n.listing,j="paid"===V?"network":"playground",N("info",`registry billingMode: ${V} — using ${j} environment`,{event:"registry_billing_mode_resolved",billingMode:V,environment:j}),U=j}const L=V??"free",O=[];let z=null;const H=p(),G=(e,t)=>{if(!H)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,n=String(a.category??""),s=String(a.operation??""),r="number"==typeof a.statusCode?a.statusCode:null,o=Date.now(),i=e.lastCategory;e.lastStatusAt=o,e.lastCategory=n,e.lastOperation=s,e.lastStatusCode=r,"PNConnectedCategory"===n&&(e.lastConnectedAt=o),N("debug","pubnub status event",{event:"pubnub_status",client:e.label,category:n,operation:s,statusCode:r,error:Boolean(a.error),affectedChannels:Array.isArray(a.affectedChannels)?a.affectedChannels.length:void 0,instanceId:c}),n&&n!==i&&N("info",`pubnub status transition [${e.label}]: ${i??"(none)"} -> ${n}`,{event:"pubnub_status_transition",client:e.label,from:i,to:n,operation:s,statusCode:r,instanceId:c})},message:()=>{e.lastMessageAt=Date.now()}}))(a),O.push(a),t.addListener(a.listener)},Y=e=>{if(!H)return;const t=O.findIndex(t=>t.pn===e);if(t<0)return;const a=O[t];try{e.removeListener(a.listener)}catch{}O.splice(t,1)};let W,Z=!1;if(t.pubnub)W=t.pubnub,Z=!1,l&&W.setToken(l);else{const e=f[U];W=s({publishKey:e.publishKey,subscribeKey:e.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0,onRetry:e=>{N("warn","pubnub transport retrying",{event:"pubnub_transport_retry",message:e,instanceId:c})}}),Z=!0,l&&W.setToken(l)}let J=f[U].subscribeKey,Q=f[U].publishKey;const X=B?new C({apiKey:q,baseUrl:B}):void 0,ee=new T({billingMode:L,subscribeKey:J,publishKey:Q,authProvider:X,baseUrl:B,createPubNub:()=>{const e=s({publishKey:Q,subscribeKey:J,userId:`${c}-taskclient`,subscribeRetryUnbounded:!1});return G("consumer-task",e),e}}),te=t.concurrency??u.concurrency,ae=t.expectedInstances??u.expectedInstances,ne=t.maxPendingBacklog,se=resolveMaxRunningTimeSec(t.maxRunningTimeSec,t.card?.runtime?.maxRunningTimeSec),re=Date.now();let oe=0;const ie=new b,de=new I,ce=new Map,le=new Map,ue=new Map,me=new Map,fe=new Map,pe=new Set,ke=new Set,ge=new Map,ye=new Set,he=new Map;let be,Ie;t.logChannel;const we=async()=>{if("function"==typeof W.setState&&Ie)try{await W.setState({channels:[Ie],state:{instanceId:c,activeTasks:oe,activeStreams:ie.activeStreamCount,concurrency:te,startedAt:re,preferredProtocolVersion:A,protocolVersions:[...E]}})}catch{}},ve=(e,t)=>{const a=ce.get(e);a&&(a.delete(t),0===a.size&&ce.delete(e))},Se=async e=>{(e=>{for(const[t,a]of ce)a.has(e)&&(a.delete(e),0===a.size&&ce.delete(t))})(e);const t=ie.releaseAllForTask(e);for(const e of t)if(e.streamClient)try{await e.streamClient.end()}catch{}},Te=async(e,t)=>{ve(e,t);const a=ie.get(e);if(0===ie.release(e,t)&&a?.streamClient){N("info","last-ref teardown",{event:"stream_registry_last_ref_teardown",streamId:e,affinity:a.affinity,releasingTaskId:t});try{await a.streamClient.end()}catch{}}we().catch(()=>{})},Ce=async(e,t)=>{const a=ie.forceRemove(e);if((e=>{ce.delete(e)})(e),a){if(a.streamClient)try{await a.streamClient.end()}catch{}for(const e of a.taskIds){const a=de.get(e);if(a){try{const n=f[a.environment??j]??f[j],r=s({publishKey:n.publishKey,subscribeKey:n.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken),await M(r,e,a.orgId,a.agentName,{type:"terminal",taskId:e,state:"failed",error:t}),r.destroy()}catch{}de.remove(e)}}await we()}},$e=async(e,t)=>{const a=de.get(e);if(!a)throw new Error(`No cached credentials for task ${e}`);const n=f[a.environment??j]??f[j],r=s({publishKey:n.publishKey,subscribeKey:n.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken);try{await M(r,e,a.orgId,a.agentName,{...t,type:"terminal",taskId:e})}finally{r.destroy()}await Se(e),de.remove(e),await we()},Ke=async(s,r,i,d,c)=>{const u=a,m="pipe"===s.taskKind,f=m?"pipe":"request",p=computeStreamDurationMinutes(s.duration,m,se);if(m&&"number"==typeof s.durationExpiresAtMs){const e=Math.max(0,s.durationExpiresAtMs-Date.now());if(e>0){const t=setTimeout(()=>{pe.add(s.taskId),c.abort()},e);ge.set(s.taskId,t)}}await M(r,s.taskId,d,u,{type:"progress",taskId:s.taskId,progress:0,state:"running"}),he.set(s.taskId,0);let k=0,g=null,y=null;const b=()=>{if(null===g)return;const e=g;g=null,k=Date.now(),M(r,s.taskId,d,u,{type:"progress",taskId:s.taskId,message:e}).catch(()=>{})},I={taskId:s.taskId,requestParts:s.requestParts??[],reportStatus:e=>{me.set(s.taskId,e);const t=Date.now();if(t-k>=1e3)g=null,k=t,M(r,s.taskId,d,u,{type:"progress",taskId:s.taskId,message:e}).catch(()=>{});else if(g=e,!y){y=setTimeout(()=>{y=null,b()},1e3-(t-k))}},get isCancelled(){return c.signal.aborted},get isExpired(){return pe.has(s.taskId)},hasStream:!!s.hasStream,cancelSignal:c.signal,taskClient:ee,consumerPublicKey:s.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(n(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}),s=a?.data;if(Buffer.isBuffer(s))return s;if(s instanceof Uint8Array)return Buffer.from(s);if(s&&"function"==typeof s.arrayBuffer){const e=await s.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,n=t?.mimeType??"application/octet-stream",o=B?{baseUrl:B,authProvider:l?new h(l):void 0,agentAuth:D}:void 0;await R(r,s.taskId,d,u,a,n,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(!s.hasStream)throw new Error("Streaming was not negotiated for this task. Ensure the agent card is registered with streaming capability.");const n=t.card.streams;if(!n||0===Object.keys(n).length)throw new Error("Agent card has no streams block. Streaming requires declared streams in the card.");const i=Object.keys(n);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=n[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 y=a?.external??!1,h=a?.metadata,b=a?.onActivate,I=l.affinity??"dedicated";if("shared"===I&&y)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"===I)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(y)throw new Error("Request tasks cannot use external streams")}let T;if("shared"===I)T=c;else{const e=(he.get(s.taskId)??0)+1;he.set(s.taskId,e),T=`${s.taskId}-${e}`}const C=o.streamChannel(T);if("shared"===I){const e=ce.get(T)?.get(s.taskId);if(e)return e}const{entry:$,isNew:K,isNewForTask:_}=ie.acquire(T,s.taskId,{direction:k,format:g,external:y,affinity:I});de.addStream(s.taskId,T);const A=e=>{if("shared"!==I)return;let t=ce.get(T);t||(t=new Map,ce.set(T,t)),t.set(s.taskId,e)};if(!_){$.setupPromise&&await $.setupPromise;const e=ce.get(T)?.get(s.taskId);if(e)return e;if($.streamClient){const e=w(T,$.streamClient,s.taskId,{releaseStream:Te});return A(e),e}throw new Error(`Stream "${T}" 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(!K)try{if($.setupPromise&&await $.setupPromise,"shared"===I&&await(async()=>{await P(r,{taskId:s.taskId,orgId:d,agentName:u,streamId:T,channel:C,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:I,phase:"activate",metadata:h,declaredStream:c})})(),$.streamClient){const e=w(T,$.streamClient,s.taskId,{releaseStream:Te});return A(e),e}if(y)return v(T,C,"",async()=>{throw new Error("External stream already activated")});throw new Error(`Stream "${T}" exists but has no client`)}catch(e){ve(T,s.taskId);try{await ie.release(T,s.taskId)}catch{}throw e}let E,x;$.setupPromise=new Promise((e,t)=>{E=e,x=t}),$.setupPromise.catch(()=>{});const M=y?"token_request":"embedded";let N;try{N=await P(r,{taskId:s.taskId,orgId:d,agentName:u,streamId:T,channel:C,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:I,phase:M,metadata:h,declaredStream:c})}catch(e){x(e),ve(T,s.taskId);try{await ie.release(T,s.taskId)}catch{}throw e}if(y){E(),$.setupPromise=null;const e=N.token??"";return v(T,C,e,async e=>{await P(r,{taskId:s.taskId,orgId:d,agentName:u,streamId:T,channel:C,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:I,phase:"activate",metadata:e?.metadata??h,declaredStream:c})})}const R=N.token;if(!R){const e=new Error("No T7a token received from stream setup handshake");x(e),ve(T,s.taskId);try{await ie.release(T,s.taskId)}catch{}throw e}const j=new e({subscribeKey:J,publishKey:Q,token:R,agentName:u,streamId:T,channel:C,format:g,direction:k,affinity:I,gating:m,bundleSizeBytes:a?.bundleSizeBytes,maxLatencyMs:a?.maxLatencyMs});$.streamClient=j;const U=w(T,j,s.taskId,{releaseStream:Te});if(A(U),E(),$.setupPromise=null,"outbound"===k||"bidirectional"===k){const e=a?.subscribeGraceMs??1e3;e>0&&await new Promise(t=>setTimeout(t,e))}return b&&($.activated=!0,$.activatePromise=S(T,U,b,Ce)),U}};let T;try{T=t.handler?await t.handler(s,I):void 0}finally{he.delete(s.taskId),y&&(clearTimeout(y),y=null),b()}const C=B?{baseUrl:B,authProvider:l?new h(l):void 0,agentAuth:D}:void 0;if(T?.artifacts&&T.artifacts.length>0)for(const e of T.artifacts){const t="string"==typeof e.data?Buffer.from(e.data,"utf-8"):e.data;await R(r,s.taskId,d,u,t,e.mimeType,e.fileName,e.outputId,C)}const $=c.signal.aborted;if(m){const e=pe.has(s.taskId),t=ke.has(s.taskId);if($||e||t){await Se(s.taskId);const a=$&&!e?"canceled":"completed";await M(r,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:a,...e?{completionReason:"duration_expired"}:{},...t?{reason:"terminated"}:{}}),de.remove(s.taskId),N("info",`Task ${s.taskId} ${a}`,{taskId:s.taskId,agentName:u,owner:i})}else N("info",`Task ${s.taskId} handler returned (pipe, no auto-terminal)`,{taskId:s.taskId,agentName:u,owner:i})}else{const e=ke.has(s.taskId);await Se(s.taskId);const t=$?"canceled":"completed";await M(r,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:t,...e?{reason:"terminated"}:{}}),de.remove(s.taskId),N("info",`Task ${s.taskId} ${t}`,{taskId:s.taskId,agentName:u,owner:i})}},_e=(()=>{let e=!1;return(t,a,n)=>{if(N("debug","pamDeniedHandler invoked",{event:"pam_handler_invoked",category:t,operation:a,statusCode:n,alreadyHandled:e,instanceId:c}),"PNAccessDeniedCategory"===t&&!e){e=!0,N("error","PAM access denied — destroying control client (agent will go silent)",{event:"pam_denied_destroy",category:t,operation:a,statusCode:n,instanceId:c,controlChannel:Ie}),N("error","PAM token expired or revoked — agent is no longer receiving tasks. Re-register the agent to resume.",{event:"pam_denied_user_message",instanceId:c});try{W.destroy()}catch{}}}})(),Ae={status:e=>{const t=e,a=String(t.category??""),n=String(t.operation??""),s="number"==typeof t.statusCode?t.statusCode:null;"PNConnectedCategory"===a&&N("debug","pubnub control client connected",{event:"control_connected",controlChannel:Ie}),_e(a,n,s)},message:e=>{const n=e.message;if(!n)return;if("SwitchEnvironment"===n.type){const e=n,t=e.environment;return void(t&&t!==U&&t in f&&((e,t)=>{if(N("info",`SwitchEnvironment requested: ${U} -> ${e}`,{pamToken:t?"present":"absent"}),!t)return void N("error","SwitchEnvironment rejected: pamToken is required but was absent. The backend must include a pamToken in SwitchEnvironment messages.");const a=W,n=Z;try{a.removeListener(Ae),Ie&&a.unsubscribe({channels:[Ie]})}catch{}Y(a);const r=f[e];if(W=s({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0,onRetry:e=>{N("warn","pubnub transport retrying",{event:"pubnub_transport_retry",message:e,instanceId:c})}}),Z=!0,be=void 0,W.setToken(t),0!==ae&&"function"==typeof W.setFilterExpression&&W.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),W.addListener(Ae),G("control",W),Ie&&W.subscribe({channels:[Ie]}),n)try{a.destroy()}catch{}U=e,J=r.subscribeKey,Q=r.publishKey,ee.updateKeys(r.subscribeKey,r.publishKey),N("info",`Switched to ${e} environment`),we().catch(()=>{})})(t,e.pamToken))}(async(e,n)=>{if("StartTask"===e.type){const r=U,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(ye.has(e.taskId))return void N("warn",`Ignoring duplicate StartTask for in-flight task ${e.taskId}`,{taskId:e.taskId});const d=Boolean(n?.broadcast);if(e.protocolVersion&&!x(e.protocolVersion))return d?void N("debug",`Ignoring broadcast StartTask ${e.taskId} with unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId}):(await M(W,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"unsupported_protocol_version"},e.protocolVersion),void N("warn",`Task ${e.taskId} rejected: unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId,instanceId:c}));if("pipe"===(e.taskKind||"request")){const t=e.duration,n=e.durationExpiresAtMs;if(null==t||!Number.isInteger(t)||t<1||t>43200||"number"!=typeof n||n<=0)return N("error",`Task ${e.taskId} rejected: pipe StartTask with invalid duration (duration=${t}, durationExpiresAtMs=${n})`,{taskId:e.taskId,instanceId:c}),void await M(W,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"invalid_start_task"},e.protocolVersion)}const l=oe+ie.activeStreamCount;if(te>0&&l>=te)return d?void N("debug",`Task ${e.taskId} skipped (broadcast, at capacity ${l}/${te})`,{taskId:e.taskId,instanceId:c}):(await M(W,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"agent_at_capacity"}),void N("warn",`Task ${e.taskId} rejected: agent at capacity`,{taskId:e.taskId,instanceId:c,activeTasks:oe}));le.set(e.taskId,o),ue.set(e.taskId,i),ye.add(e.taskId),oe++,e.controlToken&&(be=e.controlToken);const u=new AbortController;fe.set(e.taskId,u);let m=null;try{const n=f[r];if(!n.publishKey||!n.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=s({publishKey:n.publishKey,subscribeKey:n.subscribeKey,userId:c,subscribeRetryUnbounded:!1}),e.writeToken&&m.setToken(e.writeToken),G(`task-sub:${e.taskId}`,m),de.set(e.taskId,{ownerId:o,orgId:i,writeToken:e.writeToken??"",agentName:a,environment:r}),await we();const{writeToken:d,controlToken:l,...p}=e;t.onStartTask?await t.onStartTask(e,m):await Ke(p,m,o,i,u)}catch(n){const s=n?.message??"Agent instance error",r=m??W;try{await Se(e.taskId)}catch{}await M(r,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:s}),t.onError&&await t.onError(e.taskId,n),N("error",s,{taskId:e.taskId}),de.remove(e.taskId)}finally{const t=ge.get(e.taskId);if(t&&(clearTimeout(t),ge.delete(e.taskId)),ye.delete(e.taskId),le.delete(e.taskId),ue.delete(e.taskId),me.delete(e.taskId),fe.delete(e.taskId),pe.delete(e.taskId),ke.delete(e.taskId),m){Y(m);try{m.destroy()}catch{}}oe--,0===oe&&be&&W.setToken(be),await we()}}else if("CancelTask"===e.type){const t=fe.get(e.taskId);t?(t.abort(),N("info",`Task ${e.taskId} cancel requested (cooperative)`,{taskId:e.taskId,instanceId:c})):de.get(e.taskId)&&await $e(e.taskId,{state:"canceled"})}else if("ExpireTask"===e.type)fe.has(e.taskId)?(pe.add(e.taskId),fe.get(e.taskId)?.abort(),N("info",`Task ${e.taskId} expired`,{taskId:e.taskId,instanceId:c})):de.get(e.taskId)&&await $e(e.taskId,{state:"completed",completionReason:"duration_expired"});else if("TerminateTask"===e.type)fe.has(e.taskId)?(ke.add(e.taskId),fe.get(e.taskId)?.abort()):de.get(e.taskId)&&await $e(e.taskId,{state:"canceled",reason:"terminated"});else if("PauseTask"===e.type){if(ye.has(e.taskId)){const t=ue.get(e.taskId)??"anonymous";await M(W,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"paused"})}}else if("ResumeTask"===e.type){if(ye.has(e.taskId)){const t=ue.get(e.taskId)??"anonymous";await M(W,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"resumed"})}}else"RetryTask"===e.type&&N("info","RetryTask received (no-op)",{taskId:e.taskId})})(n,e.userMetadata&&"object"==typeof e.userMetadata?e.userMetadata:void 0).catch(e=>{N("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})})}};W.addListener(Ae),G("control",W),H&&null===z&&(z=setInterval(()=>{const e=Date.now();let t=!1;const a=O.map(a=>{const n=null!==a.lastStatusAt?e-a.lastStatusAt:null,s=_isDiagEntryStale({lastStatusAt:a.lastStatusAt,lastCategory:a.lastCategory,now:e,thresholdMs:6e4});s&&(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:n,msSinceConnected:null!==a.lastConnectedAt?e-a.lastConnectedAt:null,msSinceMessage:null!==a.lastMessageAt?e-a.lastMessageAt:null,subscribedChannels:r,stale:s}}),n={event:"pubnub_alive_snapshot",instanceId:c,clients:a};t?N("info","pubnub alive snapshot — STALE clients present",n):N("debug","pubnub alive snapshot",n)},1e4),"function"==typeof z.unref&&z.unref()),H&&N("info","pubnub diagnostics armed",{event:"pubnub_diagnostics_armed",snapshotIntervalMs:1e4,staleThresholdMs:6e4,instanceId:c}),i(a,{instanceId:c,billingMode:L,description:t.description,scaling:{expectedInstances:ae,concurrency:te,maxPendingBacklog:ne,maxRunningTimeSec:se},card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:F??t.listing,actor:`agent-instance:${c}`,baseUrl:t.baseUrl??y?.api.baseUrl,agentAuth:D}).then(e=>{if(N("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");Ie=e.controlChannel,e.pamToken&&W.setToken(e.pamToken),0!==ae&&"function"==typeof W.setFilterExpression&&W.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),W.subscribe({channels:[Ie]}),we().catch(()=>{})}).catch(e=>{const t=e instanceof Error?e.message:String(e);N("error",`failed to register agent: ${a} — ${t}`,{event:"agent_registration_failed",agentName:a,instanceId:c,error:t}),e instanceof _&&process.exit(1)}),N("info",`Agent instance ${c} started (agent name: ${a})`,{agentName:a,event:"agent_instance_started",instanceId:c});return{stop:()=>{try{null!==z&&(clearInterval(z),z=null);for(const e of O)try{e.pn.removeListener(e.listener)}catch{}O.length=0,X?.destroy(),ee.destroy(),W.removeListener(Ae),Ie&&W.unsubscribe({channels:[Ie]}),Z&&W.destroy();for(const e of ie.streamIds()){const t=ie.get(e);if(t?.streamClient)try{t.streamClient.end()}catch{}}ie.clear(),ce.clear(),de.clear()}catch{}},agentName:a,instanceId:c,get controlChannel(){return Ie},get pubnub(){return W},get subscribeKey(){return f[U].subscribeKey},taskClient:ee,publishTerminal:$e,failStream:Ce,cdmConfig:y}};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 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";const U=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 q=(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 B=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 U(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 D(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,L="playground",V=L;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},q("info","CDM config loaded — environment switching enabled",{event:"cdm_config_loaded"});const F=m("BLOCKS_API_KEY");if(!F)throw new Error("BLOCKS_API_KEY is required. Run 'blocks login --write-env' to set up credentials.");let O;const z=t.baseUrl??v?.api.baseUrl;let H,G;if(F&&z&&(O=new M(F,z),q("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:F});if(!s)throw new Error(`[AgentInstance] Agent "${a}" not found in registry. Register the agent (e.g. via \`blocks publish\`) 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.`);H=s.billingMode,G=s.listing,L="paid"===H?"network":"playground",q("info",`registry billingMode: ${H} — using ${L} environment`,{event:"registry_billing_mode_resolved",billingMode:H,environment:L}),V=L}const Y=H??"free",W=[];let Z=null;const J=p("diagnostics"),Q=()=>{let e=!1;return{status:t=>{const a=h(t),s=b.has(a);s&&!e?(e=!0,q("warn",`connectivity degraded: ${a}`,{event:"transport_degraded",category:a,instanceId:c})):!s&&e&&w.has(a)&&(e=!1,q("info","connectivity restored",{event:"transport_restored",category:a,instanceId:c}))}}},X=(e,t)=>{if(!J)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),q("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&&q("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),W.push(a),t.addListener(a.listener)},ee=e=>{if(!J)return;const t=W.findIndex(t=>t.pn===e);if(t<0)return;const a=W[t];try{e.removeListener(a.listener)}catch{}W.splice(t,1)};let te,ae=!1;if(t.pubnub)te=t.pubnub,ae=!1,l&&te.setToken(l);else{const e=f[V];te=n({publishKey:e.publishKey,subscribeKey:e.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0}),ae=!0,l&&te.setToken(l)}let se=f[V].subscribeKey,ne=f[V].publishKey;const re=z?new E({apiKey:F,baseUrl:z}):void 0,oe=new A({billingMode:Y,subscribeKey:se,publishKey:ne,authProvider:re,baseUrl:z,createPubNub:()=>{const e=n({publishKey:ne,subscribeKey:se,userId:`${c}-taskclient`,subscribeRetryUnbounded:!1});return X("consumer-task",e),e}}),ie=t.concurrency??u.concurrency,de=t.expectedInstances??u.expectedInstances,ce=t.maxPendingBacklog,le=resolveMaxRunningTimeSec(t.maxRunningTimeSec,t.card?.runtime?.maxRunningTimeSec),ue=Date.now();let me=0;const fe=new T,pe=new C,ke=new Map,ge=new Map,he=new Map,ye=new Map,Ie=new Map,be=new Set,we=new Set,ve=new Map,Se=new Set,Te=new Map;let Ce,$e;t.logChannel;const _e=async()=>{if("function"==typeof te.setState&&$e)try{await te.setState({channels:[$e],state:{instanceId:c,activeTasks:me,activeStreams:fe.activeStreamCount,concurrency:ie,startedAt:ue,preferredProtocolVersion:R,protocolVersions:[...j]}})}catch{}},Ke=(e,t)=>{const a=ke.get(e);a&&(a.delete(t),0===a.size&&ke.delete(e))},Ae=async e=>{(e=>{for(const[t,a]of ke)a.has(e)&&(a.delete(e),0===a.size&&ke.delete(t))})(e);const t=fe.releaseAllForTask(e);for(const e of t)if(e.streamClient)try{await e.streamClient.end()}catch{}},Ee=async(e,t)=>{Ke(e,t);const a=fe.get(e);if(0===fe.release(e,t)&&a?.streamClient){q("info","last-ref teardown",{event:"stream_registry_last_ref_teardown",streamId:e,affinity:a.affinity,releasingTaskId:t});try{await a.streamClient.end()}catch{}}_e().catch(()=>{})},xe=async(e,t)=>{const a=fe.forceRemove(e);if((e=>{ke.delete(e)})(e),a){if(a.streamClient)try{await a.streamClient.end()}catch{}for(const e of a.taskIds){const a=pe.get(e);if(a){try{const s=f[a.environment??L]??f[L],r=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken),await U(r,e,a.orgId,a.agentName,{type:"terminal",taskId:e,state:"failed",error:t}),r.destroy()}catch{}pe.remove(e)}}await _e()}},Me=async(e,t)=>{const a=pe.get(e);if(!a)throw new Error(`No cached credentials for task ${e}`);const s=f[a.environment??L]??f[L],r=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken);try{await U(r,e,a.orgId,a.agentName,{...t,type:"terminal",taskId:e})}finally{r.destroy()}await Ae(e),pe.remove(e),await _e()},Ne=async(n,r,i,d,c)=>{const u=a,m="pipe"===n.taskKind,f=m?"pipe":"request",p=computeStreamDurationMinutes(n.duration,m,le);if(m&&"number"==typeof n.durationExpiresAtMs){const e=Math.max(0,n.durationExpiresAtMs-Date.now());if(e>0){const t=setTimeout(()=>{be.add(n.taskId),c.abort()},e);ve.set(n.taskId,t)}}await U(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,progress:0,state:"running"}),Te.set(n.taskId,0);let k=0,g=null,h=null;const y=()=>{if(null===g)return;const e=g;g=null,k=Date.now(),U(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,message:e}).catch(()=>{})},I={taskId:n.taskId,requestParts:n.requestParts??[],reportStatus:e=>{ye.set(n.taskId,e);const t=Date.now();if(t-k>=1e3)g=null,k=t,U(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,message:e}).catch(()=>{});else if(g=e,!h){h=setTimeout(()=>{h=null,y()},1e3-(t-k))}},get isCancelled(){return c.signal.aborted},get isExpired(){return be.has(n.taskId)},hasStream:!!n.hasStream,cancelSignal:c.signal,taskClient:oe,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=z?{baseUrl:z,authProvider:l?new S(l):void 0,agentAuth:O}:void 0;await B(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=(Te.get(n.taskId)??0)+1;Te.set(n.taskId,e),w=`${n.taskId}-${e}`}const v=o.streamChannel(w);if("shared"===b){const e=ke.get(w)?.get(n.taskId);if(e)return e}const{entry:S,isNew:T,isNewForTask:C}=fe.acquire(w,n.taskId,{direction:k,format:g,external:h,affinity:b});pe.addStream(n.taskId,w);const A=e=>{if("shared"!==b)return;let t=ke.get(w);t||(t=new Map,ke.set(w,t)),t.set(n.taskId,e)};if(!C){S.setupPromise&&await S.setupPromise;const e=ke.get(w)?.get(n.taskId);if(e)return e;if(S.streamClient){const e=$(w,S.streamClient,n.taskId,{releaseStream:Ee});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 D(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:Ee});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){Ke(w,n.taskId);try{await fe.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 D(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),Ke(w,n.taskId);try{await fe.release(w,n.taskId)}catch{}throw e}if(h){E(),S.setupPromise=null;const e=N.token??"";return _(w,v,e,async e=>{await D(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),Ke(w,n.taskId);try{await fe.release(w,n.taskId)}catch{}throw e}const j=new e({subscribeKey:se,publishKey:ne,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:Ee});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,xe)),P}};let b;try{b=t.handler?await t.handler(n,I):void 0}finally{Te.delete(n.taskId),h&&(clearTimeout(h),h=null),y()}const w=z?{baseUrl:z,authProvider:l?new S(l):void 0,agentAuth:O}:void 0;if(b?.artifacts&&b.artifacts.length>0)for(const e of b.artifacts){const t="string"==typeof e.data?Buffer.from(e.data,"utf-8"):e.data;await B(r,n.taskId,d,u,t,e.mimeType,e.fileName,e.outputId,w)}const v=c.signal.aborted;if(m){const e=be.has(n.taskId),t=we.has(n.taskId);if(v||e||t){await Ae(n.taskId);const a=v&&!e?"canceled":"completed";await U(r,n.taskId,d,u,{type:"terminal",taskId:n.taskId,state:a,...e?{completionReason:"duration_expired"}:{},...t?{reason:"terminated"}:{}}),pe.remove(n.taskId),q("info",`Task ${n.taskId} ${a}`,{taskId:n.taskId,agentName:u,owner:i})}else q("info",`Task ${n.taskId} handler returned (pipe, no auto-terminal)`,{taskId:n.taskId,agentName:u,owner:i})}else{const e=we.has(n.taskId);await Ae(n.taskId);const t=v?"canceled":"completed";await U(r,n.taskId,d,u,{type:"terminal",taskId:n.taskId,state:t,...e?{reason:"terminated"}:{}}),pe.remove(n.taskId),q("info",`Task ${n.taskId} ${t}`,{taskId:n.taskId,agentName:u,owner:i})}},Re=(()=>{let e=!1;return(t,a)=>{const s=h(t),n="number"==typeof t.statusCode?t.statusCode:null;if(q("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,q("error","access denied — destroying control client (agent will go silent)",{event:"access_denied_destroy",category:s,operation:y(a),statusCode:n,instanceId:c,controlChannel:$e}),q("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{te.destroy()}catch{}}}})();let je=Q();const Pe={status:e=>{const t=e,a=String(t.category??""),s=String(t.operation??"");"PNConnectedCategory"===a&&q("debug","control client connected",{event:"control_connected",controlChannel:$e}),Re(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!==V&&t in f&&((e,t)=>{if(q("info",`SwitchEnvironment requested: ${V} -> ${e}`,{pamToken:t?"present":"absent"}),!t)return void q("error","SwitchEnvironment rejected: pamToken is required but was absent. The backend must include a pamToken in SwitchEnvironment messages.");const a=te,s=ae;try{a.removeListener(Pe),a.removeListener(je),$e&&a.unsubscribe({channels:[$e]})}catch{}ee(a);const r=f[e];if(te=n({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0}),ae=!0,Ce=void 0,te.setToken(t),0!==de&&"function"==typeof te.setFilterExpression&&te.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),je=Q(),te.addListener(Pe),te.addListener(je),X("control",te),$e&&te.subscribe({channels:[$e]}),s)try{a.destroy()}catch{}V=e,se=r.subscribeKey,ne=r.publishKey,oe.updateKeys(r.subscribeKey,r.publishKey),q("info",`Switched to ${e} environment`),_e().catch(()=>{})})(t,e.pamToken))}(async(e,s)=>{if("StartTask"===e.type){const r=V,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(Se.has(e.taskId))return void q("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 q("debug",`Ignoring broadcast StartTask ${e.taskId} with unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId}):(await U(te,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"unsupported_protocol_version"},e.protocolVersion),void q("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 q("error",`Task ${e.taskId} rejected: pipe StartTask with invalid duration (duration=${t}, durationExpiresAtMs=${s})`,{taskId:e.taskId,instanceId:c}),void await U(te,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"invalid_start_task"},e.protocolVersion)}const l=me+fe.activeStreamCount;if(ie>0&&l>=ie)return d?void q("debug",`Task ${e.taskId} skipped (broadcast, at capacity ${l}/${ie})`,{taskId:e.taskId,instanceId:c}):(await U(te,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"agent_at_capacity"}),void q("warn",`Task ${e.taskId} rejected: agent at capacity`,{taskId:e.taskId,instanceId:c,activeTasks:me}));ge.set(e.taskId,o),he.set(e.taskId,i),Se.add(e.taskId),me++,e.controlToken&&(Ce=e.controlToken);const u=new AbortController;Ie.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),X(`task-sub:${e.taskId}`,m),pe.set(e.taskId,{ownerId:o,orgId:i,writeToken:e.writeToken??"",agentName:a,environment:r}),await _e();const{writeToken:d,controlToken:l,...p}=e;t.onStartTask?await t.onStartTask(e,m):await Ne(p,m,o,i,u)}catch(s){const n=s?.message??"Agent instance error",r=m??te;try{await Ae(e.taskId)}catch{}await U(r,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:n}),t.onError&&await t.onError(e.taskId,s),q("error",n,{taskId:e.taskId}),pe.remove(e.taskId)}finally{const t=ve.get(e.taskId);if(t&&(clearTimeout(t),ve.delete(e.taskId)),Se.delete(e.taskId),ge.delete(e.taskId),he.delete(e.taskId),ye.delete(e.taskId),Ie.delete(e.taskId),be.delete(e.taskId),we.delete(e.taskId),m){ee(m);try{m.destroy()}catch{}}me--,0===me&&Ce&&te.setToken(Ce),await _e()}}else if("CancelTask"===e.type){const t=Ie.get(e.taskId);t?(t.abort(),q("info",`Task ${e.taskId} cancel requested (cooperative)`,{taskId:e.taskId,instanceId:c})):pe.get(e.taskId)&&await Me(e.taskId,{state:"canceled"})}else if("ExpireTask"===e.type)Ie.has(e.taskId)?(be.add(e.taskId),Ie.get(e.taskId)?.abort(),q("info",`Task ${e.taskId} expired`,{taskId:e.taskId,instanceId:c})):pe.get(e.taskId)&&await Me(e.taskId,{state:"completed",completionReason:"duration_expired"});else if("TerminateTask"===e.type)Ie.has(e.taskId)?(we.add(e.taskId),Ie.get(e.taskId)?.abort()):pe.get(e.taskId)&&await Me(e.taskId,{state:"canceled",reason:"terminated"});else if("PauseTask"===e.type){if(Se.has(e.taskId)){const t=he.get(e.taskId)??"anonymous";await U(te,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"paused"})}}else if("ResumeTask"===e.type){if(Se.has(e.taskId)){const t=he.get(e.taskId)??"anonymous";await U(te,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"resumed"})}}else"RetryTask"===e.type&&q("info","RetryTask received (no-op)",{taskId:e.taskId})})(s,e.userMetadata&&"object"==typeof e.userMetadata?e.userMetadata:void 0).catch(e=>{q("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})})}};te.addListener(Pe),te.addListener(je),X("control",te),J&&null===Z&&(Z=setInterval(()=>{const e=Date.now();let t=!1;const a=W.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?q("info","transport alive snapshot — STALE clients present",s):q("debug","transport alive snapshot",s)},1e4),"function"==typeof Z.unref&&Z.unref()),J&&q("info","transport diagnostics armed",{event:"transport_diagnostics_armed",snapshotIntervalMs:1e4,staleThresholdMs:6e4,instanceId:c}),i(a,{instanceId:c,billingMode:Y,description:t.description,scaling:{expectedInstances:de,concurrency:ie,maxPendingBacklog:ce,maxRunningTimeSec:le},card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:G??t.listing,actor:`agent-instance:${c}`,baseUrl:t.baseUrl??v?.api.baseUrl,agentAuth:O}).then(e=>{if(q("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");$e=e.controlChannel,e.pamToken&&te.setToken(e.pamToken),0!==de&&"function"==typeof te.setFilterExpression&&te.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),te.subscribe({channels:[$e]}),_e().catch(()=>{})}).catch(e=>{const t=e instanceof Error?e.message:String(e);q("error",`failed to register agent: ${a} — ${t}`,{event:"agent_registration_failed",agentName:a,instanceId:c,error:t}),e instanceof N&&process.exit(1)}),q("info",`Agent instance ${c} started (agent name: ${a})`,{agentName:a,event:"agent_instance_started",instanceId:c});return{stop:()=>{try{null!==Z&&(clearInterval(Z),Z=null);for(const e of W)try{e.pn.removeListener(e.listener)}catch{}W.length=0,J&&q("info","transport diagnostics disarmed",{event:"transport_diagnostics_disarmed",instanceId:c}),re?.destroy(),oe.destroy(),te.removeListener(Pe),te.removeListener(je),$e&&te.unsubscribe({channels:[$e]}),ae&&te.destroy();for(const e of fe.streamIds()){const t=fe.get(e);if(t?.streamClient)try{t.streamClient.end()}catch{}}fe.clear(),ke.clear(),pe.clear()}catch{}},agentName:a,instanceId:c,get controlChannel(){return $e},get pubnub(){return te},get subscribeKey(){return f[V].subscribeKey},taskClient:oe,publishTerminal:Me,failStream:xe,cdmConfig:v}};export const artifactUtils={buildArtifactRef:t,shouldInlineArtifact:a};
@@ -2,10 +2,10 @@
2
2
  * Agent Registry - REST-based agent card storage and discovery.
3
3
  *
4
4
  * All registry operations (register, fetch, get, remove) use the server-side
5
- * REST endpoints at /api/v1/registry/agents which handle App Context operations,
5
+ * REST endpoints at /api/v1/registry/agents which handle registry metadata operations,
6
6
  * membership indexing, and audit events server-side.
7
7
  */
8
- import { registryAllChannel, registrySkillChannel, registryVisibilityChannel, registryLogChannel, normalizeSkillSlug } from './channel-manager.js';
8
+ import { registryAllChannel, registryTagChannel, registryVisibilityChannel, registryLogChannel, normalizeTagSlug } from './channel-manager.js';
9
9
  import type { AgentAuth } from './agent-auth.js';
10
10
  /**
11
11
  * Scaling configuration for multi-instance support.
@@ -17,9 +17,9 @@ export interface AgentScaling {
17
17
  maxRunningTimeSec?: number;
18
18
  }
19
19
  /**
20
- * Skill object within an AgentCard.
20
+ * Tag object within an AgentCard.
21
21
  */
22
- export interface AgentSkill {
22
+ export interface AgentTag {
23
23
  id: string;
24
24
  name: string;
25
25
  description?: string;
@@ -29,7 +29,7 @@ export interface AgentSkill {
29
29
  * Agent card payload (input schema) — authored by developers in agent-card.json.
30
30
  *
31
31
  * Uses the 9-section structure: identity, capabilities, io, streams,
32
- * security, services, skills, extensions, runtime.
32
+ * security, services, tags, extensions, runtime.
33
33
  */
34
34
  export interface AgentCard {
35
35
  identity: {
@@ -89,7 +89,7 @@ export interface AgentCard {
89
89
  services?: {
90
90
  webhooks?: boolean;
91
91
  };
92
- skills: AgentSkill[];
92
+ tags: AgentTag[];
93
93
  extensions?: Record<string, unknown>;
94
94
  runtime?: {
95
95
  handler?: string;
@@ -125,7 +125,7 @@ export interface OutputAgentCard {
125
125
  streams?: AgentCard['streams'];
126
126
  security?: AgentCard['security'];
127
127
  services?: AgentCard['services'];
128
- skills: AgentSkill[];
128
+ tags: AgentTag[];
129
129
  extensions?: Record<string, unknown>;
130
130
  }
131
131
  /**
@@ -138,8 +138,8 @@ export interface AgentEntry {
138
138
  displayName: string;
139
139
  /** Human-readable description */
140
140
  description?: string;
141
- /** Skill objects from agent card */
142
- skills?: AgentSkill[];
141
+ /** Tag objects from agent card */
142
+ tags?: AgentTag[];
143
143
  /** Multi-instance scaling configuration */
144
144
  scaling?: AgentScaling;
145
145
  /** Full agent card payload */
@@ -171,8 +171,8 @@ export interface ConnectAgentOptions {
171
171
  billingMode: 'free' | 'paid';
172
172
  /** Human-readable description */
173
173
  description?: string;
174
- /** Skill objects advertised by this agent */
175
- skills?: AgentSkill[];
174
+ /** Tag objects advertised by this agent */
175
+ tags?: AgentTag[];
176
176
  /** Multi-instance scaling configuration */
177
177
  scaling?: AgentScaling;
178
178
  /** Full agent card payload */
@@ -208,7 +208,7 @@ export interface RegistryAuditEvent {
208
208
  source: string;
209
209
  agentName?: string;
210
210
  visibility?: 'public' | 'private';
211
- skills?: string[];
211
+ tags?: string[];
212
212
  cardHash?: string;
213
213
  cardRef?: string;
214
214
  summaryHash?: string;
@@ -232,7 +232,7 @@ declare function detectDeviceOs(): string;
232
232
  * Register or update an agent in the registry via REST.
233
233
  *
234
234
  * Sends a POST request to the dedicated registry endpoint which handles
235
- * all App Context operations (UUID metadata, memberships, audit events)
235
+ * all registry metadata operations (UUID metadata, memberships, audit events)
236
236
  * server-side.
237
237
  */
238
238
  export declare function connectAgent(agentName: string, options: ConnectAgentOptions): Promise<{
@@ -261,9 +261,9 @@ export declare function fetchAgentRegistry(options?: {
261
261
  baseUrl?: string;
262
262
  }): Promise<AgentRegistryResult>;
263
263
  /**
264
- * Fetch agents filtered by skill.
264
+ * Fetch agents filtered by tag.
265
265
  */
266
- export declare function fetchAgentsBySkill(skill: string, options?: {
266
+ export declare function fetchAgentsByTag(tag: string, options?: {
267
267
  limit?: number;
268
268
  cursor?: string;
269
269
  baseUrl?: string;
@@ -283,4 +283,4 @@ export declare function removeAgent(agentName: string, options?: {
283
283
  baseUrl?: string;
284
284
  agentAuth?: AgentAuth;
285
285
  }): Promise<boolean>;
286
- export { detectDeviceOs, registryAllChannel, registrySkillChannel, registryVisibilityChannel, registryLogChannel, normalizeSkillSlug, };
286
+ export { detectDeviceOs, registryAllChannel, registryTagChannel, registryVisibilityChannel, registryLogChannel, normalizeTagSlug, };
@@ -1 +1 @@
1
- import t from"node:os";import{registryAllChannel as n,registrySkillChannel as e,registryVisibilityChannel as a,registryLogChannel as r,normalizeSkillSlug as o}from"./channel-manager.js";import{getEnv as i}from"../env.js";import{CURRENT_PROTOCOL_VERSION as s,SUPPORTED_PROTOCOL_VERSIONS as c,SDK_VERSION as l,PROTOCOL_VERSION_HEADER as u}from"./protocol-version.js";import{captureAffinity as g,injectAffinity as d}from"./write-affinity.js";function m(){try{return t.platform()}catch{return"unknown"}}async function f(t,n=3,e=500){let a;for(let r=0;r<n;r++)try{return await t()}catch(t){a=t;const o=String(t);if(!(o.includes("ENOTFOUND")||o.includes("ETIMEDOUT")||o.includes("ECONNRESET")||o.includes("NetworkIssues"))||r===n-1)throw t;const i=e*Math.pow(2,r)+100*Math.random();await new Promise(t=>setTimeout(t,i))}throw a}function h(t,n){let e=`${function(t){if(t)return t.replace(/\/+$/,"");throw new Error("[AgentRegistry] baseUrl is required: provide it via options or CDM config")}(n)}/api/v1/registry/agents`;if(t){const n=new URLSearchParams;for(const[e,a]of Object.entries(t))void 0!==a&&""!==a&&n.set(e,a);const a=n.toString();a&&(e+="?"+a)}return e}async function p(t,n){const e=new Headers(n?.headers);e.has(u)||e.set(u,s);const a={};d(a);for(const[t,n]of Object.entries(a))e.set(t,n);const r=await fetch(t,{...n,headers:e});if(g(r.headers),404===r.status)return null;if(!r.ok){const t=(await r.json().catch(()=>({}))).message??`HTTP ${r.status}`;throw new Error(`[AgentRegistry] Request failed: ${t}`)}return{data:await r.json(),status:r.status}}function y(t){return{agentName:t.agentName,displayName:t.name??t.agentName,description:t.description,skills:t.skills,scaling:t.scaling,card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:t.listing??"public",billingMode:t.billingMode,createdAt:t.registeredAt}}export async function connectAgent(t,n){if(!/^[a-zA-Z0-9_]+$/.test(t))throw new Error("agentName must contain only alphanumeric characters and underscores (no hyphens)");h(void 0,n.baseUrl);const e=i("BLOCKS_CLI_VERSION"),a={agentName:t,instanceId:n.instanceId,billingMode:n.billingMode,listing:n.listing,expectedInstances:n.scaling?.expectedInstances,concurrency:n.scaling?.concurrency,maxPendingBacklog:n.scaling?.maxPendingBacklog,maxRunningTimeSec:n.scaling?.maxRunningTimeSec,deviceOs:m(),sdkLanguage:"Node",sdkVersion:l,protocolVersions:[...c],preferredProtocolVersion:s};let r;if(e&&(a.cliVersion=e),!n.agentAuth)throw new Error("[AgentRegistry] agentAuth is required. Set BLOCKS_API_KEY and pass agentAuth to connectAgent.");const o=await f(async()=>n.agentAuth.init(a));return r=o.pamToken??void 0,{pamToken:r,agentId:o.agentId,controlChannel:o.controlChannel}}export async function getAgent(t,n){const e=h({agentName:t},n?.baseUrl),a=n?.apiKey?{headers:{Authorization:`Bearer ${n.apiKey}`}}:void 0,r=await f(async()=>p(e,a));return r?y(r.data.agent):null}export async function fetchAgentRegistry(t){const n={include:"full"};t?.limit&&(n.limit=String(t.limit)),t?.cursor&&(n.cursor=t.cursor);const e=h(n,t?.baseUrl),a=await f(async()=>p(e));return a?{agents:a.data.agents.map(y),next:a.data.next??void 0,totalCount:a.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentsBySkill(t,n){const e={include:"full",skill:t};n?.limit&&(e.limit=String(n.limit)),n?.cursor&&(e.cursor=n.cursor);const a=h(e,n?.baseUrl),r=await f(async()=>p(a));return r?{agents:r.data.agents.map(y),next:r.data.next??void 0,totalCount:r.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentsByListing(t,n){const e={include:"full",listing:t};n?.limit&&(e.limit=String(n.limit)),n?.cursor&&(e.cursor=n.cursor);const a=h(e,n?.baseUrl),r=await f(async()=>p(a));return r?{agents:r.data.agents.map(y),next:r.data.next??void 0,totalCount:r.data.totalCount}:{agents:[],totalCount:0}}export async function removeAgent(t,n){const e=h({agentName:t},n?.baseUrl);if(n?.agentAuth){return 404!==(await f(()=>n.agentAuth.authenticatedFetch(e,{method:"DELETE"}))).status}const a=i("BLOCKS_API_KEY");return null!==await f(async()=>p(e,{method:"DELETE",headers:{...a?{Authorization:`Bearer ${a}`}:{}}}))}export{m as detectDeviceOs,n as registryAllChannel,e as registrySkillChannel,a as registryVisibilityChannel,r as registryLogChannel,o as normalizeSkillSlug};
1
+ import t from"node:os";import{registryAllChannel as n,registryTagChannel as e,registryVisibilityChannel as a,registryLogChannel as r,normalizeTagSlug as o}from"./channel-manager.js";import{getEnv as i}from"../env.js";import{CURRENT_PROTOCOL_VERSION as s,SUPPORTED_PROTOCOL_VERSIONS as c,SDK_VERSION as l,PROTOCOL_VERSION_HEADER as u}from"./protocol-version.js";import{captureAffinity as g,injectAffinity as d}from"./write-affinity.js";function m(){try{return t.platform()}catch{return"unknown"}}async function f(t,n=3,e=500){let a;for(let r=0;r<n;r++)try{return await t()}catch(t){a=t;const o=String(t);if(!(o.includes("ENOTFOUND")||o.includes("ETIMEDOUT")||o.includes("ECONNRESET")||o.includes("NetworkIssues"))||r===n-1)throw t;const i=e*Math.pow(2,r)+100*Math.random();await new Promise(t=>setTimeout(t,i))}throw a}function h(t,n){let e=`${function(t){if(t)return t.replace(/\/+$/,"");throw new Error("[AgentRegistry] baseUrl is required: provide it via options or CDM config")}(n)}/api/v1/registry/agents`;if(t){const n=new URLSearchParams;for(const[e,a]of Object.entries(t))void 0!==a&&""!==a&&n.set(e,a);const a=n.toString();a&&(e+="?"+a)}return e}async function p(t,n){const e=new Headers(n?.headers);e.has(u)||e.set(u,s);const a={};d(a);for(const[t,n]of Object.entries(a))e.set(t,n);const r=await fetch(t,{...n,headers:e});if(g(r.headers),404===r.status)return null;if(!r.ok){const t=(await r.json().catch(()=>({}))).message??`HTTP ${r.status}`;throw new Error(`[AgentRegistry] Request failed: ${t}`)}return{data:await r.json(),status:r.status}}function y(t){return{agentName:t.agentName,displayName:t.name??t.agentName,description:t.description,tags:t.tags,scaling:t.scaling,card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:t.listing??"public",billingMode:t.billingMode,createdAt:t.registeredAt}}export async function connectAgent(t,n){if(!/^[a-zA-Z0-9_]+$/.test(t))throw new Error("agentName must contain only alphanumeric characters and underscores (no hyphens)");!function(t){if(t&&"object"==typeof t&&"skills"in t)throw new Error("[blocks-sdk] agent-card field `skills` was renamed to `tags`. Action: rename the field in your code (or in any agent-card.json you load) — same shape, only the key name changes. This SDK ships no compatibility alias. If you publish via `blocks-cli`, upgrading the CLI rewrites the field for you, but programmatic callers (this SDK) must rename.")}(n.card);h(void 0,n.baseUrl);const e=i("BLOCKS_CLI_VERSION"),a={agentName:t,instanceId:n.instanceId,billingMode:n.billingMode,listing:n.listing,expectedInstances:n.scaling?.expectedInstances,concurrency:n.scaling?.concurrency,maxPendingBacklog:n.scaling?.maxPendingBacklog,maxRunningTimeSec:n.scaling?.maxRunningTimeSec,deviceOs:m(),sdkLanguage:"Node",sdkVersion:l,protocolVersions:[...c],preferredProtocolVersion:s};let r;if(e&&(a.cliVersion=e),!n.agentAuth)throw new Error("[AgentRegistry] agentAuth is required. Set BLOCKS_API_KEY and pass agentAuth to connectAgent.");const o=await f(async()=>n.agentAuth.init(a));return r=o.pamToken??void 0,{pamToken:r,agentId:o.agentId,controlChannel:o.controlChannel}}export async function getAgent(t,n){const e=h({agentName:t},n?.baseUrl),a=n?.apiKey?{headers:{Authorization:`Bearer ${n.apiKey}`}}:void 0,r=await f(async()=>p(e,a));return r?y(r.data.agent):null}export async function fetchAgentRegistry(t){const n={include:"full"};t?.limit&&(n.limit=String(t.limit)),t?.cursor&&(n.cursor=t.cursor);const e=h(n,t?.baseUrl),a=await f(async()=>p(e));return a?{agents:a.data.agents.map(y),next:a.data.next??void 0,totalCount:a.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentsByTag(t,n){const e={include:"full",tag:t};n?.limit&&(e.limit=String(n.limit)),n?.cursor&&(e.cursor=n.cursor);const a=h(e,n?.baseUrl),r=await f(async()=>p(a));return r?{agents:r.data.agents.map(y),next:r.data.next??void 0,totalCount:r.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentsByListing(t,n){const e={include:"full",listing:t};n?.limit&&(e.limit=String(n.limit)),n?.cursor&&(e.cursor=n.cursor);const a=h(e,n?.baseUrl),r=await f(async()=>p(a));return r?{agents:r.data.agents.map(y),next:r.data.next??void 0,totalCount:r.data.totalCount}:{agents:[],totalCount:0}}export async function removeAgent(t,n){const e=h({agentName:t},n?.baseUrl);if(n?.agentAuth){return 404!==(await f(()=>n.agentAuth.authenticatedFetch(e,{method:"DELETE"}))).status}const a=i("BLOCKS_API_KEY");return null!==await f(async()=>p(e,{method:"DELETE",headers:{...a?{Authorization:`Bearer ${a}`}:{}}}))}export{m as detectDeviceOs,n as registryAllChannel,e as registryTagChannel,a as registryVisibilityChannel,r as registryLogChannel,o as normalizeTagSlug};
@@ -33,7 +33,37 @@ export interface AuthProvider {
33
33
  * calls are no-ops after the first success.
34
34
  */
35
35
  ensureReady?(): Promise<void>;
36
+ /**
37
+ * Returns the recorded permanent-refresh error when the provider is in
38
+ * a known-broken state (e.g. proactive refresh exhausted its retries),
39
+ * or null otherwise. The transport calls this before issuing any
40
+ * authenticated request and throws the error directly so consumers see
41
+ * the typed `AuthRefreshFailedError` instead of an opaque downstream
42
+ * 401. Implementations without proactive refresh return null / omit.
43
+ */
44
+ getLastAuthError?(): Error | null;
36
45
  }
46
+ /**
47
+ * Pre-flight gate used by authenticated SDK paths (RPC, file upload,
48
+ * TaskClient methods) to surface a recorded permanent-refresh error
49
+ * before issuing the request.
50
+ *
51
+ * Behavior:
52
+ * - No `getLastAuthError` / no recorded error -> returns immediately.
53
+ * - Error recorded -> attempts one reactive refresh via
54
+ * `onAuthFailure()`. On success the error clears atomically with the
55
+ * new token apply and this returns silently (caller proceeds with the
56
+ * refreshed token). On failure the error stays recorded and this
57
+ * throws it so the caller sees `AuthRefreshFailedError` instead of an
58
+ * opaque downstream 401.
59
+ *
60
+ * The two-phase shape (record-then-retry-then-throw) preserves the
61
+ * documented reactive recovery path: a transient outage that exhausts
62
+ * proactive retries and then resolves still lets the next authenticated
63
+ * call recover, instead of permanently wedging the client until it is
64
+ * rebuilt.
65
+ */
66
+ export declare function preflightAuthOrThrow(provider: AuthProvider | undefined): Promise<void>;
37
67
  /**
38
68
  * Wraps a static JWT string. No refresh capability -- onAuthFailure()
39
69
  * always returns false.
@@ -1 +1 @@
1
- export class StaticAuthProvider{constructor(t){this.token=t}getAuthHeader(){return`Bearer ${this.token}`}async onAuthFailure(){return!1}}
1
+ export async function preflightAuthOrThrow(t){if(!t?.getLastAuthError)return;if(!t.getLastAuthError())return;await t.onAuthFailure();const r=t.getLastAuthError();if(r)throw r}export class StaticAuthProvider{constructor(t){this.token=t}getAuthHeader(){return`Bearer ${this.token}`}async onAuthFailure(){return!1}}
@@ -6,14 +6,14 @@
6
6
  * - agent.{agentId}.control - Control plane per agent ID
7
7
  * - obs.{agentName}.log - Agent-level observability
8
8
  *
9
- * Registry channels (App Context membership indexes):
9
+ * Registry channels (membership indexes):
10
10
  * - registry.all - All agents (membership index)
11
11
  * - registry.public - Public agents (membership index)
12
12
  * - registry.private - Private agents (membership index)
13
- * - registry.skill.{skill} - Agents with specific skill (membership index)
13
+ * - registry.tag.{tag} - Agents with specific tag (membership index)
14
14
  * - registry.log - Audit log channel (pub/sub with history)
15
15
  *
16
- * App Context (channel metadata, not pub/sub):
16
+ * Channel metadata (not pub/sub):
17
17
  * - uuid:{agentName} - Agent card stored as User Object
18
18
  *
19
19
  * All channels respect PubNub's 3-level maximum depth constraint.
@@ -25,12 +25,12 @@
25
25
  */
26
26
  export declare const registryAllChannel: () => string;
27
27
  /**
28
- * Registry skill channel - membership index for agents with a skill.
29
- * Format: registry.skill.{skill}
28
+ * Registry tag channel - membership index for agents with a tag.
29
+ * Format: registry.tag.{tag}
30
30
  *
31
- * Skill slugs are normalized: lowercase, alphanumeric + `.` or `_`.
31
+ * Tag slugs are normalized: lowercase, alphanumeric + `.` or `_`.
32
32
  */
33
- export declare const registrySkillChannel: (skill: string) => string;
33
+ export declare const registryTagChannel: (tag: string) => string;
34
34
  /**
35
35
  * Registry visibility channel - membership index for public/private agents.
36
36
  * Format: registry.public or registry.private
@@ -42,7 +42,7 @@ export declare const registryVisibilityChannel: (isPublic: boolean) => string;
42
42
  */
43
43
  export declare const registryLogChannel: () => string;
44
44
  /**
45
- * Normalize a skill string to a stable slug.
45
+ * Normalize a tag string to a stable slug.
46
46
  * - Lowercase
47
47
  * - Replace non-alphanumeric characters (except `.` and `_`) with `_`
48
48
  * - Collapse multiple underscores
@@ -52,7 +52,7 @@ export declare const registryLogChannel: () => string;
52
52
  * - "text.embeddings" -> "text.embeddings"
53
53
  * - "Image Generation" -> "image_generation"
54
54
  */
55
- export declare const normalizeSkillSlug: (skill: string) => string;
55
+ export declare const normalizeTagSlug: (tag: string) => string;
56
56
  /**
57
57
  * Validates that an owner ID is not a reserved prefix.
58
58
  */
@@ -1 +1 @@
1
- export const registryAllChannel=()=>"registry.all";export const registrySkillChannel=r=>`registry.skill.${normalizeSkillSlug(r)}`;export const registryVisibilityChannel=r=>r?"registry.public":"registry.private";export const registryLogChannel=()=>"registry.log";export const normalizeSkillSlug=r=>r.toLowerCase().replace(/[^a-z0-9._]/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"");const r=["agent","obs","sys","u","anonymous","system","stream"];export function validateOwnerId(e){return!(!e||"string"!=typeof e)&&!r.includes(e.toLowerCase())}export class ChannelManager{constructor(r={}){if(!r.agentName)throw new Error("agentName is required for ChannelManager");this.agentName=r.agentName}getAgentName(){return this.agentName}taskChannel(r,e){if(!e)throw new Error("orgId required for task channel");if(!r)throw new Error("taskId required for task channel");return`u.${e}.${r}`}controlChannel(r){return`agent.${r}.control`}obsChannel(r){return`obs.${r??this.agentName}.log`}userTaskPattern(r){if(!r)throw new Error("orgId required for user task pattern");return`u.${r}.*`}agentWildcard(r){return`agent.${r??this.agentName}.*`}streamChannel(r,e){if(!r)throw new Error("streamId required for stream channel");return`stream.${e??this.agentName}.${r}`}streamWildcard(r){return`stream.${r??this.agentName}.*`}}export const createChannelManager=r=>new ChannelManager({agentName:r});export const taskChannel=(r,e)=>{if(!e)throw new Error("orgId required for task channel");if(!r)throw new Error("taskId required for task channel");return`u.${e}.${r}`};export const userTaskPattern=r=>{if(!r)throw new Error("orgId required for user task pattern");return`u.${r}.*`};export const streamChannel=(r,e)=>{if(!r)throw new Error("agentName required for stream channel");if(!e)throw new Error("streamId required for stream channel");return`stream.${r}.${e}`};
1
+ export const registryAllChannel=()=>"registry.all";export const registryTagChannel=r=>`registry.tag.${normalizeTagSlug(r)}`;export const registryVisibilityChannel=r=>r?"registry.public":"registry.private";export const registryLogChannel=()=>"registry.log";export const normalizeTagSlug=r=>r.toLowerCase().replace(/[^a-z0-9._]/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"");const r=["agent","obs","sys","u","anonymous","system","stream"];export function validateOwnerId(e){return!(!e||"string"!=typeof e)&&!r.includes(e.toLowerCase())}export class ChannelManager{constructor(r={}){if(!r.agentName)throw new Error("agentName is required for ChannelManager");this.agentName=r.agentName}getAgentName(){return this.agentName}taskChannel(r,e){if(!e)throw new Error("orgId required for task channel");if(!r)throw new Error("taskId required for task channel");return`u.${e}.${r}`}controlChannel(r){return`agent.${r}.control`}obsChannel(r){return`obs.${r??this.agentName}.log`}userTaskPattern(r){if(!r)throw new Error("orgId required for user task pattern");return`u.${r}.*`}agentWildcard(r){return`agent.${r??this.agentName}.*`}streamChannel(r,e){if(!r)throw new Error("streamId required for stream channel");return`stream.${e??this.agentName}.${r}`}streamWildcard(r){return`stream.${r??this.agentName}.*`}}export const createChannelManager=r=>new ChannelManager({agentName:r});export const taskChannel=(r,e)=>{if(!e)throw new Error("orgId required for task channel");if(!r)throw new Error("taskId required for task channel");return`u.${e}.${r}`};export const userTaskPattern=r=>{if(!r)throw new Error("orgId required for user task pattern");return`u.${r}.*`};export const streamChannel=(r,e)=>{if(!r)throw new Error("agentName required for stream channel");if(!e)throw new Error("streamId required for stream channel");return`stream.${r}.${e}`};