@ziggs-ai/api-client 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,87 +1,51 @@
1
- import { type InboxOperatorAgentEntry } from './http/InboxClient.js';
2
1
  type OpenFn = () => Promise<unknown>;
3
2
  type CloseFn = (handle: unknown) => Promise<void>;
4
- export interface StartAgentOptions {
5
- /**
6
- * Keep this host running until it is stopped explicitly — exempt from the
7
- * idle sweep, last choice for LRU eviction. For agents the operator named
8
- * (launcher `preStart` / `WAKE_AGENTS`): they were started because someone
9
- * decided they must be up, not because work arrived, so "no work for a
10
- * while" is not a reason to retire them.
11
- */
12
- pinned?: boolean;
13
- }
14
3
  export interface ConnectionManagerMeta {
15
4
  domain?: string;
16
5
  expertise?: string[];
17
6
  tags?: string[];
18
7
  [key: string]: unknown;
19
8
  }
20
- export interface ConnectionManagerOptions {
21
- maxActive?: number;
22
- idleTimeoutMs?: number;
23
- /**
24
- * Fleet control (delivery law, phase 4): the operator key whose owned agents
25
- * this manager lazily starts. `start()` long-polls `GET /inbox/operator` with
26
- * this key; when an agent's slice shows actionable content, it starts that
27
- * agent's host (which then consumes via its own per-agent inbox loop). No
28
- * socket, no push notification — lazy process management driven by the poll. `wsUrl`
29
- * is accepted for back-compat but unused (the poll is HTTP).
30
- */
31
- control?: {
32
- wsUrl?: string;
33
- operatorKey?: string;
34
- };
35
- /**
36
- * Gate on the operator-poll lazy start (ZIG-910). Called for each agent
37
- * slice with actionable content before its host is started; return false to
38
- * leave the host down this pass. The slice stays in later envelopes (reading
39
- * never acks), so the gate is consulted again on every poll. Explicit
40
- * `startAgent()` calls are not gated. Default: everything actionable starts.
41
- */
42
- shouldLazyStart?: (entry: InboxOperatorAgentEntry) => boolean;
43
- }
44
9
  export interface QueryFilter {
45
10
  domain?: string;
46
11
  expertise?: string[];
47
12
  tags?: string[];
48
13
  }
14
+ /**
15
+ * ConnectionManager — the set of agent hosts this process runs.
16
+ *
17
+ * Every registered agent is connected and stays connected (ZIG-1108). There is
18
+ * no subset, no cap, no idle sweep and no lifecycle policy: an agent is alive
19
+ * because its launcher registered it, and the only thing that takes it down is
20
+ * the process exiting. Whether work has arrived recently is not a fact about
21
+ * whether an agent should exist.
22
+ *
23
+ * What this replaced: an operator long-poll that asked the backend which agents
24
+ * had mail, an LRU that evicted hosts past `maxActive`, an idle timer that slept
25
+ * quiet hosts, and a `pinned` flag to exempt the ones an operator had named.
26
+ * Together they made liveness depend on someone remembering to edit a Terraform
27
+ * string, and cost a fleet-wide sweep per poll to find out nothing had changed.
28
+ */
49
29
  export declare class ConnectionManager {
50
- private maxActive;
51
- private idleTimeoutMs;
52
- private _operatorKey;
53
- private _polling;
54
- private _pollDone;
55
30
  private _entries;
56
31
  private _active;
57
32
  private _meta;
58
33
  private _starting;
59
- private _pinned;
60
- private _shouldLazyStart;
61
- constructor({ maxActive, idleTimeoutMs, control, shouldLazyStart }?: ConnectionManagerOptions);
34
+ constructor();
62
35
  register(id: string, openFn: OpenFn, closeFn: CloseFn, meta?: ConnectionManagerMeta): void;
63
- start(operatorKey?: string): void;
64
- stop(): Promise<void>;
65
36
  /**
66
- * Fleet lazy-start loop: long-poll the operator inbox and start a host for any
67
- * owned agent whose slice is actionable and not already running. The started
68
- * host consumes + acks via its own per-agent inbox loop, so this loop never
69
- * acks — it only decides which hosts to run. Errors retry with backoff+jitter;
70
- * the loop never exits until `stop()` (nothing to resynchronize).
37
+ * Connect every registered agent. One host's failure to come up is reported
38
+ * and does not hold back the rest — its own transport retries, and a fleet
39
+ * where 28 of 29 are live is not a launch worth aborting.
71
40
  */
72
- private _runOperatorPoll;
73
- startAgent(id: string, { pinned }?: StartAgentOptions): Promise<unknown>;
41
+ connectAll(): Promise<void>;
42
+ startAgent(id: string): Promise<unknown>;
74
43
  sleep(id: string): Promise<void>;
75
44
  sleepAll(): Promise<void>;
76
- touch(id: string): void;
77
45
  list(): string[];
78
46
  listActive(): string[];
79
- listPinned(): string[];
80
47
  get size(): number;
81
48
  query({ domain, expertise, tags }?: QueryFilter): string[];
82
49
  getMeta(id: string): ConnectionManagerMeta | undefined;
83
- private _scheduleIdle;
84
- private _resetTimer;
85
- private _evictLRU;
86
50
  }
87
51
  export {};
@@ -1,36 +1,29 @@
1
- import { InboxClient } from './http/InboxClient.js';
2
1
  import { runtimeLog } from './shared/runtimeLog.js';
3
- import { isRateLimited } from './shared/rateLimit.js';
4
- const OPERATOR_POLL_WAIT_SECONDS = 25;
5
- const POLL_BACKOFF_BASE_MS = 1_000;
6
- const POLL_BACKOFF_MAX_MS = 30_000;
7
- function sleep(ms) {
8
- return new Promise((resolve) => setTimeout(resolve, ms));
9
- }
2
+ /**
3
+ * ConnectionManager — the set of agent hosts this process runs.
4
+ *
5
+ * Every registered agent is connected and stays connected (ZIG-1108). There is
6
+ * no subset, no cap, no idle sweep and no lifecycle policy: an agent is alive
7
+ * because its launcher registered it, and the only thing that takes it down is
8
+ * the process exiting. Whether work has arrived recently is not a fact about
9
+ * whether an agent should exist.
10
+ *
11
+ * What this replaced: an operator long-poll that asked the backend which agents
12
+ * had mail, an LRU that evicted hosts past `maxActive`, an idle timer that slept
13
+ * quiet hosts, and a `pinned` flag to exempt the ones an operator had named.
14
+ * Together they made liveness depend on someone remembering to edit a Terraform
15
+ * string, and cost a fleet-wide sweep per poll to find out nothing had changed.
16
+ */
10
17
  export class ConnectionManager {
11
- maxActive;
12
- idleTimeoutMs;
13
- _operatorKey;
14
- _polling;
15
- _pollDone;
16
18
  _entries;
17
19
  _active;
18
20
  _meta;
19
21
  _starting;
20
- _pinned;
21
- _shouldLazyStart;
22
- constructor({ maxActive = 50, idleTimeoutMs = 60_000, control, shouldLazyStart } = {}) {
23
- this.maxActive = maxActive;
24
- this.idleTimeoutMs = idleTimeoutMs;
25
- this._shouldLazyStart = shouldLazyStart ?? null;
26
- this._operatorKey = control?.operatorKey ?? null;
27
- this._polling = false;
28
- this._pollDone = null;
22
+ constructor() {
29
23
  this._entries = new Map();
30
24
  this._active = new Map();
31
25
  this._meta = new Map();
32
26
  this._starting = new Map();
33
- this._pinned = new Set();
34
27
  }
35
28
  register(id, openFn, closeFn, meta) {
36
29
  if (!id)
@@ -43,103 +36,34 @@ export class ConnectionManager {
43
36
  if (meta)
44
37
  this._meta.set(id, meta);
45
38
  }
46
- start(operatorKey) {
47
- if (operatorKey)
48
- this._operatorKey = operatorKey;
49
- if (this._polling || !this._operatorKey)
50
- return;
51
- this._polling = true;
52
- this._pollDone = this._runOperatorPoll(this._operatorKey);
53
- }
54
- async stop() {
55
- this._polling = false;
56
- if (this._pollDone)
57
- await this._pollDone.catch(() => { });
58
- this._pollDone = null;
59
- await this.sleepAll();
60
- }
61
39
  /**
62
- * Fleet lazy-start loop: long-poll the operator inbox and start a host for any
63
- * owned agent whose slice is actionable and not already running. The started
64
- * host consumes + acks via its own per-agent inbox loop, so this loop never
65
- * acks — it only decides which hosts to run. Errors retry with backoff+jitter;
66
- * the loop never exits until `stop()` (nothing to resynchronize).
40
+ * Connect every registered agent. One host's failure to come up is reported
41
+ * and does not hold back the rest — its own transport retries, and a fleet
42
+ * where 28 of 29 are live is not a launch worth aborting.
67
43
  */
68
- async _runOperatorPoll(operatorKey) {
69
- // No agentId → operator-level multiplex read (transport auth only).
70
- const inbox = new InboxClient(operatorKey);
71
- let consecutiveErrors = 0;
72
- while (this._polling) {
73
- try {
74
- // Roster scoping (ZIG-965): only agents this manager can actually
75
- // start are worth sweeping server-side. Without it a partial launcher
76
- // (ONLY_AGENTS) paid for a sweep of the owner's whole seeded fleet.
77
- //
78
- // Already-running agents are excluded: this loop's only job is
79
- // lazy-start, and a running host polls its own inbox. Sending them
80
- // made the server answer for agents whose entry we then discarded
81
- // below — pure duplicate work, growing with how healthy the fleet is.
82
- const startable = [...this._entries.keys()].filter((id) => !this._active.has(id));
83
- const env = await inbox.getOperatorInbox({
84
- waitSeconds: OPERATOR_POLL_WAIT_SECONDS,
85
- agents: startable,
86
- });
87
- consecutiveErrors = 0;
88
- for (const entry of env.agents) {
89
- const id = entry.agentId;
90
- // Only slices with actionable content are listed; start the host if
91
- // it isn't already running and we know how to open it.
92
- if (!id || this._active.has(id) || !this._entries.has(id))
93
- continue;
94
- if (this._shouldLazyStart && !this._shouldLazyStart(entry))
95
- continue;
96
- this.startAgent(id).catch((err) => runtimeLog.warn('ConnectionManager', `lazy-start("${id}") failed: ${err.message}`));
97
- }
98
- }
99
- catch (err) {
100
- if (!this._polling)
101
- break;
102
- consecutiveErrors++;
103
- // ZIG-1019: same rule as the per-agent loop — when the server throttles
104
- // us it also says for how long, and retrying sooner just spends more of
105
- // the budget we were told we are out of.
106
- const throttleMs = isRateLimited(err) ? (err.retryAfterMs ?? 30_000) : null;
107
- if (throttleMs !== null) {
108
- consecutiveErrors = 0;
109
- runtimeLog.warn('ConnectionManager', `operator poll throttled: ${err.message} — waiting ${Math.round(throttleMs)}ms as instructed`);
110
- await sleep(throttleMs);
111
- continue;
112
- }
113
- const backoff = Math.min(POLL_BACKOFF_MAX_MS, POLL_BACKOFF_BASE_MS * 2 ** (consecutiveErrors - 1));
114
- const jitter = backoff * (0.5 + (consecutiveErrors % 7) / 14);
115
- runtimeLog.warn('ConnectionManager', `operator poll failed (#${consecutiveErrors}): ${err.message} — retrying in ~${Math.round(jitter)}ms`);
116
- await sleep(jitter);
117
- }
118
- }
44
+ async connectAll() {
45
+ const ids = [...this._entries.keys()];
46
+ // The count is the cost: every registered agent is one WS connection plus
47
+ // one inbox long-poll, held for the life of the process. There is no cap,
48
+ // so a registration list that balloons shows up here first — loudly, with
49
+ // the number, before the sockets open.
50
+ runtimeLog.info('ConnectionManager', `connecting all ${ids.length} registered agent(s) — one connection + inbox long-poll each, for the life of the process`);
51
+ await Promise.all(ids.map((id) => this.startAgent(id).catch((err) => runtimeLog.warn('ConnectionManager', `start("${id}") failed: ${err.message}`))));
119
52
  }
120
- async startAgent(id, { pinned = false } = {}) {
121
- // Recorded before any await so a pin is never lost to a concurrent
122
- // unpinned start of the same id.
123
- if (pinned)
124
- this._pinned.add(id);
53
+ async startAgent(id) {
125
54
  const existing = this._active.get(id);
126
- if (existing) {
127
- this._resetTimer(id);
128
- return existing.handle;
129
- }
55
+ if (existing)
56
+ return existing;
130
57
  const pending = this._starting.get(id);
131
58
  if (pending)
132
59
  return pending;
133
60
  const entry = this._entries.get(id);
134
61
  if (!entry)
135
62
  throw new Error(`[ConnectionManager] unknown id: "${id}"`);
136
- if (this._active.size >= this.maxActive)
137
- await this._evictLRU();
138
63
  const startPromise = (async () => {
139
64
  try {
140
65
  const handle = await entry.openFn();
141
- this._active.set(id, { handle, timer: null, lastActive: Date.now() });
142
- this._scheduleIdle(id);
66
+ this._active.set(id, handle);
143
67
  return handle;
144
68
  }
145
69
  finally {
@@ -150,22 +74,19 @@ export class ConnectionManager {
150
74
  return startPromise;
151
75
  }
152
76
  async sleep(id) {
153
- const entry = this._active.get(id);
154
- if (!entry)
77
+ if (!this._active.has(id))
155
78
  return;
156
- clearTimeout(entry.timer);
79
+ const handle = this._active.get(id);
157
80
  this._active.delete(id);
158
81
  const reg = this._entries.get(id);
159
82
  if (reg)
160
- await reg.closeFn(entry.handle);
83
+ await reg.closeFn(handle);
161
84
  }
162
85
  async sleepAll() {
163
86
  await Promise.all([...this._active.keys()].map(id => this.sleep(id)));
164
87
  }
165
- touch(id) { this._resetTimer(id); }
166
88
  list() { return [...this._entries.keys()]; }
167
89
  listActive() { return [...this._active.keys()]; }
168
- listPinned() { return [...this._pinned]; }
169
90
  get size() { return this._entries.size; }
170
91
  query({ domain, expertise, tags } = {}) {
171
92
  const results = [];
@@ -181,54 +102,4 @@ export class ConnectionManager {
181
102
  return results;
182
103
  }
183
104
  getMeta(id) { return this._meta.get(id); }
184
- _scheduleIdle(id) {
185
- const entry = this._active.get(id);
186
- if (!entry)
187
- return;
188
- clearTimeout(entry.timer);
189
- // A pinned host has no idle timer at all. Empty polls don't count as
190
- // activity (only delivered items call touch()), so a pinned agent nobody
191
- // talks to would otherwise be swept — and with lazy start unable to keep
192
- // up at fleet size, nothing brings it back until the process restarts.
193
- if (this._pinned.has(id)) {
194
- entry.timer = null;
195
- return;
196
- }
197
- entry.timer = setTimeout(() => {
198
- this.sleep(id).catch(err => runtimeLog.warn('ConnectionManager', `idle sleep("${id}") failed: ${err.message}`));
199
- }, this.idleTimeoutMs);
200
- }
201
- _resetTimer(id) {
202
- const entry = this._active.get(id);
203
- if (!entry)
204
- return;
205
- entry.lastActive = Date.now();
206
- this._scheduleIdle(id);
207
- }
208
- async _evictLRU() {
209
- // Pinned hosts are the last to go: pick the LRU unpinned one, and only
210
- // fall back to a pinned one when every active host is pinned. maxActive
211
- // stays a hard cap — a pin outranks other agents, not the capacity limit —
212
- // but say so, because it means more agents were pinned than can run.
213
- const oldestOf = (ids) => {
214
- let oldest = null;
215
- for (const id of ids) {
216
- const entry = this._active.get(id);
217
- const oldestEntry = oldest ? this._active.get(oldest) : null;
218
- if (!entry)
219
- continue;
220
- if (!oldest || !oldestEntry || entry.lastActive < oldestEntry.lastActive)
221
- oldest = id;
222
- }
223
- return oldest;
224
- };
225
- const active = [...this._active.keys()];
226
- const victim = oldestOf(active.filter(id => !this._pinned.has(id))) ?? oldestOf(active);
227
- if (!victim)
228
- return;
229
- if (this._pinned.has(victim)) {
230
- runtimeLog.warn('ConnectionManager', `evicting pinned "${victim}": all ${active.length} active hosts are pinned and maxActive=${this.maxActive} is reached — pin fewer agents or raise maxActive`);
231
- }
232
- await this.sleep(victim);
233
- }
234
105
  }
@@ -1,5 +1,5 @@
1
1
  import { claimOpenAgreement } from '../http/agreementFlows.js';
2
- import { linkIsReachOnly, linkSummary } from './links.js';
2
+ import { linkIsReachOnly } from './links.js';
3
3
  import { fullCreds } from './types.js';
4
4
  /**
5
5
  * ZIG-1021 — the one claim verb. Quests, standing offers, and link invites
@@ -29,7 +29,7 @@ export const agreementClaimCapability = {
29
29
  status: 'linked',
30
30
  kind,
31
31
  message: `Link invite claimed — you are now linked. ${linkIsReachOnly(env)}`,
32
- agreement: linkSummary(agreement),
32
+ agreement,
33
33
  };
34
34
  }
35
35
  return {
@@ -97,7 +97,7 @@ export const requestConnectionCapability = {
97
97
  note: env.surface === 'mcp'
98
98
  ? 'A connection-consent card is now in the chat awaiting your principal. Tell the human now (pull-only MCP has no push) — they approve it right in the chat. ' +
99
99
  'Once approved, the connection + grant appear in ziggs_connection_list for ziggs_connection_proxy.'
100
- : 'A connection-consent card is now in the chat awaiting your principal — they approve it right there (ZIG-798 flow). ' +
100
+ : 'A connection-consent card is now in the chat awaiting your principal — they approve it right there. ' +
101
101
  'Once approved, the connection + grant appear in grant_list (scopeKind=connection) / connection_list_grants for connection_proxy.',
102
102
  };
103
103
  }
@@ -1,6 +1,6 @@
1
1
  export { type CapabilitySurface, type CapabilityAnnotation, type CapabilityParam, type CapabilityEnv, type CapabilityDefinition, fullCreds, rethrowWithContext, } from './types.js';
2
2
  export { PAYMENT_CAPABILITIES, paymentBalanceCapability, paymentTransferCapability, paymentWaitForApprovalCapability, paymentHoldCapability, paymentReleaseCapability, paymentResolveWalletCapability, paymentIssueGrantCapability, paymentAttenuateGrantCapability, paymentRevokeGrantCapability, } from './payments.js';
3
- export { LINK_CAPABILITIES, createLinkInviteCapability, listLinksCapability, linkSummary, linkIsReachOnly, } from './links.js';
3
+ export { LINK_CAPABILITIES, createLinkInviteCapability, listLinksCapability, linkIsReachOnly, } from './links.js';
4
4
  export { AGREEMENT_CAPABILITIES, agreementClaimCapability } from './agreements.js';
5
5
  export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
6
6
  export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
@@ -1,6 +1,6 @@
1
1
  export { fullCreds, rethrowWithContext, } from './types.js';
2
2
  export { PAYMENT_CAPABILITIES, paymentBalanceCapability, paymentTransferCapability, paymentWaitForApprovalCapability, paymentHoldCapability, paymentReleaseCapability, paymentResolveWalletCapability, paymentIssueGrantCapability, paymentAttenuateGrantCapability, paymentRevokeGrantCapability, } from './payments.js';
3
- export { LINK_CAPABILITIES, createLinkInviteCapability, listLinksCapability, linkSummary, linkIsReachOnly, } from './links.js';
3
+ export { LINK_CAPABILITIES, createLinkInviteCapability, listLinksCapability, linkIsReachOnly, } from './links.js';
4
4
  export { AGREEMENT_CAPABILITIES, agreementClaimCapability } from './agreements.js';
5
5
  export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
6
6
  export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
@@ -1,12 +1,4 @@
1
- import type { Agreement } from '../types.js';
2
1
  import { type CapabilityDefinition, type CapabilityEnv } from './types.js';
3
- /**
4
- * ZIG-670 — link-shaped summaries, not raw agreement documents: the money
5
- * block, approvals array, and Mongo internals are noise on a trust
6
- * relationship. ZIG-956 moved this into the shared layer so the MCP mutations
7
- * return it too (they used to leak the raw agreement doc).
8
- */
9
- export declare function linkSummary(a: Agreement): Record<string, unknown>;
10
2
  /**
11
3
  * A link is reach-only — the follow-up move differs by surface tool names.
12
4
  *
@@ -18,27 +18,6 @@ function webAppOrigin(env) {
18
18
  function inviteShareUrl(env, agreementId) {
19
19
  return `${webAppOrigin(env)}/connect/${agreementId}`;
20
20
  }
21
- /**
22
- * ZIG-670 — link-shaped summaries, not raw agreement documents: the money
23
- * block, approvals array, and Mongo internals are noise on a trust
24
- * relationship. ZIG-956 moved this into the shared layer so the MCP mutations
25
- * return it too (they used to leak the raw agreement doc).
26
- */
27
- export function linkSummary(a) {
28
- return {
29
- agreementId: a.agreementId,
30
- status: a.status,
31
- proposalStatus: a.proposalStatus,
32
- parties: {
33
- creatorAgent: a.parties?.creatorAgent ?? null,
34
- providerAgent: a.parties?.providerAgent ?? null,
35
- creator: a.parties?.creator ?? null,
36
- proposedTo: a.parties?.proposedTo ?? null,
37
- },
38
- ...(a.description ? { description: a.description } : {}),
39
- createdAt: a.createdAt,
40
- };
41
- }
42
21
  /**
43
22
  * A link is reach-only — the follow-up move differs by surface tool names.
44
23
  *
@@ -106,7 +85,7 @@ export const createLinkInviteCapability = {
106
85
  message: env.surface === 'mcp'
107
86
  ? `Open link invite created, ${seatNote}. Give the human shareUrl and nothing else — it is the whole invite. A recipient with no Ziggs account signs up straight from that page, no beta code needed, and accepting the link is part of the same step; a recipient who would rather their own assistant do the wiring can hand it the same URL, because the page carries the MCP server address and the claim instructions in its markup. Either way the recipient needs their own assistant connected before the link carries anything.`
108
87
  : `Open link invite created, ${seatNote}. shareUrl is the whole invite: a recipient with no Ziggs account signs up straight from that page and accepts the link in the same step, and an assistant handed the same URL reads the connect instructions off it. They still need an assistant connected before the link carries anything. No agent id needed on either side. Revoke with agreement_revoke to disable.`,
109
- agreement: linkSummary(agreement),
88
+ agreement,
110
89
  };
111
90
  },
112
91
  sdkOptions: { isAgreementCreation: true },
@@ -136,12 +115,11 @@ export const listLinksCapability = {
136
115
  engagementKind: 'link',
137
116
  ...(status === 'all' ? {} : { status }),
138
117
  }, fullCreds(env));
139
- const summaries = links.map(linkSummary);
140
118
  const hasActive = links.some((a) => a.status === 'active');
141
119
  return {
142
- count: summaries.length,
120
+ count: links.length,
143
121
  status,
144
- links: summaries,
122
+ links,
145
123
  ...(hasActive ? { nextSteps: linkIsReachOnly(env) } : {}),
146
124
  };
147
125
  },
@@ -21,6 +21,9 @@ export function rethrowWithContext(error, prefix) {
21
21
  wrapped.status = e.status;
22
22
  if (e.body !== undefined)
23
23
  wrapped.body = e.body;
24
+ // ZIG-1124 — keep the machine code so toolError can classify without prose.
25
+ if (typeof e.code === 'string' && e.code)
26
+ wrapped.code = e.code;
24
27
  wrapped['cause'] = error;
25
28
  throw wrapped;
26
29
  }
@@ -46,6 +46,33 @@ export interface ProposeDirectInput extends ProposeTerms {
46
46
  export type ProposeBroadcastInput = Omit<ProposeDirectInput, 'proposedTo'> & {
47
47
  audience?: BroadcastAudience;
48
48
  };
49
+ /**
50
+ * A trust link's agreement, as every caller sees it.
51
+ *
52
+ * A link is reach, not commerce: it carries no money, no escrow, no execution
53
+ * state and no approvals ledger. Handing the raw document over anyway put Mongo
54
+ * bookkeeping in front of an LLM, which is what ZIG-957 forbade.
55
+ *
56
+ * Lives here rather than in `capabilities/links.ts` because this is where the
57
+ * rule is applied (ZIG-1111); that module re-exports it so the public name is
58
+ * unchanged.
59
+ */
60
+ export declare function linkSummary(a: Agreement): Record<string, unknown>;
61
+ /**
62
+ * Every agreement document this client parses passes through here.
63
+ *
64
+ * The rule — a link is returned as its summary, anything else verbatim — used to
65
+ * be written out at six call sites across the SDK runner, the MCP tools and the
66
+ * capability layer, and three verbs never got it: `agreement_counter`,
67
+ * `agreement_fulfill` and `agreement_subcontract` returned the raw document
68
+ * (ZIG-1111). Applying it at the parse boundary means a new verb inherits the
69
+ * rule instead of remembering to opt in, and no surface can word its own verdict.
70
+ *
71
+ * Typed as `Agreement` on the way out: every key the summary keeps IS an
72
+ * Agreement field, so this narrows a document rather than returning a different
73
+ * shape.
74
+ */
75
+ export declare function shapeAgreement(a: Agreement): Agreement;
49
76
  /** @deprecated Alias for {@link ProposeDirectInput}. */
50
77
  export type ProposeAgreementData = ProposeDirectInput;
51
78
  export declare function proposeAgreement(proposalData: ProposeDirectInput, creds: Creds): Promise<Agreement>;
@@ -152,6 +179,7 @@ export interface GetMyAgreementsFilters {
152
179
  partyOnly?: boolean;
153
180
  }
154
181
  export declare function getMyAgreements(filters: GetMyAgreementsFilters | undefined, creds: Creds): Promise<Agreement[]>;
182
+ /** One agreement, shaped: a link comes back as its summary. */
155
183
  export declare function getAgreement(agreementId: string, creds: Creds): Promise<Agreement | null>;
156
184
  export interface CreateAgreementBody {
157
185
  proposedToId?: string;
@@ -181,6 +209,8 @@ export declare function fulfillAgreement(agreementId: string, creds: Creds): Pro
181
209
  ok: boolean;
182
210
  agreement: Agreement;
183
211
  }>;
212
+ /** What an open-broadcast claim turned out to be. ⚠️ SYNC: backend AgreementOpenService. */
213
+ export type ClaimedKind = 'link' | 'offer' | 'quest' | 'hand-off';
184
214
  /**
185
215
  * Claim an open agreement (ZIG-524 phase 2 / ZIG-525):
186
216
  * - open link invite (`engagementKind: link`, proposedTo everyone)
@@ -193,6 +223,7 @@ export declare function fulfillAgreement(agreementId: string, creds: Creds): Pro
193
223
  export declare function claimAgreement(agreementId: string, creds: Creds): Promise<{
194
224
  ok: boolean;
195
225
  agreement: Agreement;
226
+ kind?: ClaimedKind;
196
227
  }>;
197
228
  /**
198
229
  * Link types a caller may ASK for — the server's `CALLER_LINK_TYPES`, not its
@@ -251,6 +282,7 @@ export declare class AgreementClient {
251
282
  claimAgreement(id: string): Promise<{
252
283
  ok: boolean;
253
284
  agreement: Agreement;
285
+ kind?: ClaimedKind;
254
286
  }>;
255
287
  linkToChat(id: string, chatId: string, linkType?: ChatLinkType): Promise<unknown>;
256
288
  listChats(id: string): Promise<unknown[]>;