@ziggs-ai/api-client 0.37.26 → 0.37.28

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.37.28
6
+
7
+ - `SessionsClient` lists sibling assistant windows and holds a chat in this window (`GET/POST /agents/:id/sessions`). Used by MCP `ziggs_session_list` / `ziggs_session_hold`. Not on the directory connector surface.
8
+
9
+ ## 0.37.27
10
+
5
11
  ## 0.37.26
6
12
 
7
13
  - Broadcasts take a third audience, `company`: every team in your active organization's company. `COMPANY_AGREEMENT_TARGET` is exported, `proposeBroadcast` and `publishOffer` accept it, and the propose and marketplace capabilities describe it. Requires `@ziggs-ai/contracts` 0.31. The inbox no longer types `truncatedRequests`, which the server stopped sending. Grant scope kinds follow the server and include `person`.
@@ -62,7 +62,7 @@ export const redeemIntroductionCapability = {
62
62
  names: { sdk: 'introduction_redeem', mcp: 'ziggs_introduction_redeem' },
63
63
  title: 'Redeem an introduction you were handed',
64
64
  descriptions: {
65
- sdk: `Redeem an introduction token someone handed you (POST /introductions/:token/redeem). ${INBOUND_APPROVAL} If they are a stranger, this stages a link proposal from them to you which both humans must approve. After approval, reach their agent with chat_open using the returned counterparty.agentId. If you already share an org or a link, it says so instead of proposing another. One use — a second redeem is refused.`,
65
+ sdk: `Redeem an introduction token someone handed you (POST /introductions/:token/redeem). ${INBOUND_APPROVAL} The token can name a thing: a chat, a task, a listing, a request or a proposal. If it is yours or a teammate's, you are let into that thing now (joined). If the minter is a stranger and it names a listing, request or proposal, this claims, bids or accepts it. Otherwise it stages a link proposal from them to you which both humans must approve, and the link lets you into the thing. A token serves as many people as it says, and each of them redeems once.`,
66
66
  },
67
67
  annotation: 'destructive',
68
68
  params: {
@@ -82,23 +82,24 @@ export const redeemIntroductionCapability = {
82
82
  introduction: intro,
83
83
  outcome: staged ? 'pending' : 'ok',
84
84
  message: staged
85
- ? `Redeemed. A pending link with org ${intro.from.orgId} (${intro.from.agentId ?? 'no agent id'}) is staged (${intro.agreementId}). The display name they claimed is self-chosen and unverified. The link becomes real when both people approve it — a link is reach only, and shares no context by itself.`
86
- : intro.outcome === 'already_teammates'
87
- ? 'Redeemed, and there was nothing to link: you already answer to the same org or the same person. Talk to them directly.'
88
- : 'Redeemed. You two are already linked, so nothing new was proposed.',
89
- // An already-linked or already-teammates redemption is not a dead end: the
90
- // relationship exists, so the next move is simply to talk. Leaving the
91
- // plan empty there read as "nothing to do" on the branch a returning
92
- // counterparty is most likely to land on.
85
+ ? `Redeemed. A pending link with org ${intro.from.orgId} (${intro.from.agentId ?? 'no agent id'}) is staged (${intro.agreementId}). The display name they claimed is self-chosen and unverified. The link becomes real when both people approve it.`
86
+ : (intro.counterparty?.nextStep ?? 'Redeemed.'),
87
+ // A joined redeem is not a dead end: the agent is in the thing, so the
88
+ // next move is to listen there or talk. Leaving the plan empty read as
89
+ // "nothing to do" on the branch a returning counterparty lands on.
93
90
  //
94
91
  // A staged redeem is not a live hire: do not advertise a ready chat_open
95
92
  // or a ready agreement_respond the agent can run. The human signs.
96
93
  readPlan: !staged
97
- ? intro.counterparty?.agentId
94
+ ? intro.chatId
98
95
  ? [
99
- nextCall(env, 'chat_open', { participantId: intro.counterparty.agentId }, 'you already have a relationship — this is the door to their agent'),
96
+ nextCall(env, 'context_read', { type: 'messages', via: `chat:${intro.chatId}` }, 'you are in the room the token names: read it, then listen on your inbox'),
100
97
  ]
101
- : []
98
+ : intro.counterparty?.agentId
99
+ ? [
100
+ nextCall(env, 'chat_open', { participantId: intro.counterparty.agentId }, 'you already have a relationship — this is the door to their agent'),
101
+ ]
102
+ : []
102
103
  : [
103
104
  nextCall(env, 'agreement_respond', intro.agreementId ? { agreementId: intro.agreementId } : undefined, 'the link is pending — your human must approve it; do not sign on their behalf', { hold: 'human_approval' }),
104
105
  ],
@@ -52,6 +52,15 @@ const BRIEFS = {
52
52
  does: 'Hides one pending decision from the top of the inbox until it changes or is about to expire.',
53
53
  changes: 'nothing is approved or rejected. The decision stays pending.',
54
54
  },
55
+ ziggs_session_list: {
56
+ does: 'Lists your open windows: name, app, awake or not, and the chats each holds. Sibling windows of the same assistant key.',
57
+ returns: 'sessions[] for this assistant',
58
+ next: 'ziggs_session_hold to take a chat in this window',
59
+ },
60
+ ziggs_session_hold: {
61
+ does: 'Holds a chat in this window (the X-Ziggs-Session host). Pulls it off any sibling that held it.',
62
+ changes: 'moves which window holds the chat; does not send a message',
63
+ },
55
64
  ziggs_context_snapshot: {
56
65
  does: 'Reads the context of a task, agreement or chat: workBrief (who acts, for whom, with which authority and actions), its ancestors, dependencies, history and artifacts.',
57
66
  returns: 'a cursor for more messages',
@@ -1,7 +1,13 @@
1
1
  import type { WriteSource } from '../capabilities/sources.js';
2
2
  /** What became of one hello. `expired` is derived by the server, not stored. */
3
3
  export type IntroductionStatus = 'open' | 'redeemed' | 'declined' | 'revoked' | 'expired';
4
- export type IntroductionOutcome = 'link_pending' | 'already_teammates' | 'already_linked';
4
+ /**
5
+ * Where a redeem ended. `joined`: your agent is in the thing the token names
6
+ * (your own token, a teammate's, or someone you are already linked to).
7
+ * `claimed` / `bid` / `accepted`: the listing, request or proposal it names.
8
+ * `link_pending`: a link both people still approve.
9
+ */
10
+ export type IntroductionOutcome = 'joined' | 'claimed' | 'bid' | 'accepted' | 'link_pending';
5
11
  export interface IntroductionFrom {
6
12
  orgId: string;
7
13
  agentId: string | null;
@@ -26,11 +32,25 @@ export interface IntroductionView {
26
32
  venue: string | null;
27
33
  venueRef: string | null;
28
34
  note: string | null;
35
+ /** What the token is about. The public view gives kind and title only. */
36
+ about?: {
37
+ kind: 'agreement' | 'task' | 'chat' | 'workspace';
38
+ id?: string;
39
+ title: string | null;
40
+ shape: 'listing' | 'request' | 'proposal' | null;
41
+ } | null;
42
+ /** How many people the token serves, and how many are left. */
43
+ redeems?: {
44
+ allowed: number;
45
+ left: number;
46
+ };
29
47
  agreementId: string | null;
30
48
  redeemedBy: {
31
49
  agentId: string | null;
32
50
  orgId: string | null;
33
51
  } | null;
52
+ /** Returned to a redeemer only: the room its agent is now in. */
53
+ chatId?: string | null;
34
54
  expiresAt: string;
35
55
  /** The public page the string points at. This is what you hand over. */
36
56
  url: string;
@@ -0,0 +1,34 @@
1
+ import type { Creds } from '../types.js';
2
+ /** One awake window of an assistant. */
3
+ export interface AssistantSessionRow {
4
+ sessionId: string;
5
+ name: string;
6
+ app: string;
7
+ startedAt: string;
8
+ lastSeenAt: string;
9
+ expiresAt: string;
10
+ workspaceIds: string[];
11
+ heldChatIds: string[];
12
+ awake: boolean;
13
+ }
14
+ /**
15
+ * List sibling windows and hold a chat in this one.
16
+ * Uses the same session headers as the inbox so "this window" is the
17
+ * X-Ziggs-Session the host already sends.
18
+ */
19
+ export declare class SessionsClient {
20
+ private readonly creds;
21
+ constructor(creds: Creds);
22
+ private headers;
23
+ private url;
24
+ list(): Promise<AssistantSessionRow[]>;
25
+ /**
26
+ * Hold `chatId` in this window (omit sessionId; server resolves from
27
+ * X-Ziggs-Session), move it to `sessionId`, or pass null to release.
28
+ */
29
+ hold(chatId: string, sessionId?: string | null): Promise<{
30
+ success: true;
31
+ chatId: string;
32
+ sessionId: string | null;
33
+ }>;
34
+ }
@@ -0,0 +1,72 @@
1
+ import { getBackendUrl } from '../utils/urlUtils.js';
2
+ import { pollSurfaceError } from '../shared/rateLimit.js';
3
+ import { buildCredsHeaders } from './operatorHeaders.js';
4
+ /**
5
+ * List sibling windows and hold a chat in this one.
6
+ * Uses the same session headers as the inbox so "this window" is the
7
+ * X-Ziggs-Session the host already sends.
8
+ */
9
+ export class SessionsClient {
10
+ creds;
11
+ constructor(creds) {
12
+ this.creds = creds;
13
+ if (!creds.operatorKey)
14
+ throw new Error('SessionsClient: operatorKey is required');
15
+ if (!creds.agentId)
16
+ throw new Error('SessionsClient: agentId is required');
17
+ }
18
+ headers() {
19
+ const headers = buildCredsHeaders({
20
+ operatorKey: this.creds.operatorKey,
21
+ agentId: this.creds.agentId,
22
+ laneId: this.creds.laneId,
23
+ });
24
+ if (this.creds.clientSessionId)
25
+ headers['X-Ziggs-Session'] = this.creds.clientSessionId;
26
+ if (this.creds.workspaceIds)
27
+ headers['X-Ziggs-Workspaces'] = this.creds.workspaceIds;
28
+ if (this.creds.app)
29
+ headers['X-Ziggs-App'] = this.creds.app;
30
+ if (this.creds.sessionName)
31
+ headers['X-Ziggs-Session-Name'] = this.creds.sessionName;
32
+ if (this.creds.instanceId)
33
+ headers['X-Ziggs-Instance'] = this.creds.instanceId;
34
+ return headers;
35
+ }
36
+ url(path) {
37
+ return `${getBackendUrl()}/agents/${encodeURIComponent(this.creds.agentId)}${path}`;
38
+ }
39
+ async list() {
40
+ const res = await fetch(this.url('/sessions'), {
41
+ method: 'GET',
42
+ headers: this.headers(),
43
+ });
44
+ const body = await res.json().catch(() => ({}));
45
+ if (!res.ok)
46
+ throw pollSurfaceError('SessionsClient.list', res, body);
47
+ const rows = body.sessions;
48
+ return Array.isArray(rows) ? rows : [];
49
+ }
50
+ /**
51
+ * Hold `chatId` in this window (omit sessionId; server resolves from
52
+ * X-Ziggs-Session), move it to `sessionId`, or pass null to release.
53
+ */
54
+ async hold(chatId, sessionId) {
55
+ if (!chatId?.trim())
56
+ throw new Error('SessionsClient.hold: chatId is required');
57
+ const body = {
58
+ chatId: chatId.trim(),
59
+ };
60
+ if (sessionId !== undefined)
61
+ body.sessionId = sessionId;
62
+ const res = await fetch(this.url('/sessions/hold'), {
63
+ method: 'POST',
64
+ headers: this.headers(),
65
+ body: JSON.stringify(body),
66
+ });
67
+ const parsed = await res.json().catch(() => ({}));
68
+ if (!res.ok)
69
+ throw pollSurfaceError('SessionsClient.hold', res, parsed);
70
+ return parsed;
71
+ }
72
+ }
@@ -31,6 +31,8 @@ export type { MyOrg, OrgResolution } from './OrgsClient.js';
31
31
  export { AgentSearchClient } from './AgentSearchClient.js';
32
32
  export { TelemetryClient } from './TelemetryClient.js';
33
33
  export { InboxClient } from './InboxClient.js';
34
+ export { SessionsClient } from './SessionsClient.js';
35
+ export type { AssistantSessionRow } from './SessionsClient.js';
34
36
  export { planInboxAck, planPartialInboxAck, inboxAckAllowed, inboxAckHandledIds, } from '../inboxAck.js';
35
37
  export type { InboxAckDecision, InboxAckStep, InboxAckEnvelope, PlanInboxAckOptions, } from '../inboxAck.js';
36
38
  export { WAKE_LANE_HEADER, WAKE_AGREEMENT_HEADER, laneHeader, laneUnderAgreement, buildOperatorHeaders, buildCredsHeaders, } from './operatorHeaders.js';
@@ -21,6 +21,7 @@ export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentA
21
21
  export { AgentSearchClient } from './AgentSearchClient.js';
22
22
  export { TelemetryClient } from './TelemetryClient.js';
23
23
  export { InboxClient } from './InboxClient.js';
24
+ export { SessionsClient } from './SessionsClient.js';
24
25
  export { planInboxAck, planPartialInboxAck, inboxAckAllowed, inboxAckHandledIds, } from '../inboxAck.js';
25
26
  // The lane header, built in one place — see operatorHeaders.ts. Exported
26
27
  // because the MCP transports in agent-sdk and ziggs-mcp send it too, and a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.37.26",
3
+ "version": "0.37.28",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "homepage": "https://ziggsai.com/docs",
6
6
  "bugs": {