@ziggs-ai/api-client 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,19 +14,65 @@ export function nextCall(env, capabilityKey, args, why) {
14
14
  };
15
15
  }
16
16
  /**
17
- * The other party in a two-party agreement, from the perspective of `selfId`.
17
+ * The other PRINCIPAL in a two-party agreement, from the perspective of
18
+ * `selfId`.
19
+ *
20
+ * This was `peerAgentId`, and it read the `actor` columns — the agents that
21
+ * carried the paperwork. Those became courier info when a link was re-keyed to
22
+ * the two people it belongs to, and on most links they are null, so the hint it
23
+ * fed either pre-filled a call with an agent that is no longer a door or
24
+ * silently vanished because the helper returned nothing.
25
+ *
26
+ * Named for the SLOT, not for what a link happens to put in it. On a link both
27
+ * principals are guaranteed to be people, because links are person-only and
28
+ * both sides are userIds by construction — but the same slots hold an org or an
29
+ * agent on other engagement kinds, so a caller who read "person" here and
30
+ * trusted it on a hire would be wrong. The person guarantee is a link-only
31
+ * property; ask the kind before relying on it.
18
32
  *
19
33
  * Returns null rather than guessing when the row does not identify one, because
20
34
  * a pre-filled call naming the wrong counterparty is worse than no pre-filled
21
35
  * call: the caller would run it, and it would do something they did not ask for.
22
36
  */
23
- export function peerAgentId(parties, selfId) {
37
+ export function peerPrincipalId(parties, selfId) {
24
38
  if (!parties || !selfId)
25
39
  return null;
26
- const { creatorAgent, providerAgent } = parties;
27
- if (creatorAgent && creatorAgent !== selfId)
28
- return creatorAgent;
29
- if (providerAgent && providerAgent !== selfId)
30
- return providerAgent;
40
+ const creator = parties.creator?.principal;
41
+ const provider = parties.provider?.principal;
42
+ if (creator && creator !== selfId)
43
+ return creator;
44
+ if (provider && provider !== selfId)
45
+ return provider;
31
46
  return null;
32
47
  }
48
+ /**
49
+ * The peer's principal, located by finding MY side rather than by knowing my
50
+ * own principal id.
51
+ *
52
+ * An agent knows its own agent id and not the id of the person it answers for,
53
+ * so it cannot ask {@link peerPrincipalId} which of two people it is. What it
54
+ * CAN recognise is its own courier stamp: if my agent id is in one side's
55
+ * `actor`, that side is mine and the other side's principal is the peer.
56
+ *
57
+ * Note the difference from the bug this replaced. Reading the peer's `actor` as
58
+ * the peer's address was wrong — those slots are courier info, null on most
59
+ * links, and never a door. Reading MY OWN `actor` to work out which side I am on
60
+ * is sound, because I am comparing against an id I hold.
61
+ *
62
+ * Returns null when neither side carries my stamp, which is the common case for
63
+ * a link two people formed from the web. No pre-filled call is the right answer
64
+ * there: naming the wrong counterparty would get run.
65
+ */
66
+ export function peerPrincipalForCourier(parties, myAgentId) {
67
+ if (!parties || !myAgentId)
68
+ return null;
69
+ const mineIsCreator = parties.creator?.actor === myAgentId;
70
+ const mineIsProvider = parties.provider?.actor === myAgentId;
71
+ // Both sides stamped with me is one estate on both ends, not a peer.
72
+ if (mineIsCreator === mineIsProvider)
73
+ return null;
74
+ const peer = mineIsCreator
75
+ ? parties.provider?.principal
76
+ : parties.creator?.principal;
77
+ return peer && peer !== myAgentId ? peer : null;
78
+ }
@@ -44,6 +44,12 @@ export interface ProposeDirectInput extends ProposeTerms {
44
44
  */
45
45
  export type ProposeBroadcastInput = Omit<ProposeDirectInput, 'proposedTo'> & {
46
46
  audience?: BroadcastAudience;
47
+ /**
48
+ * Exact-match triage for supplier hosts: a plain string they compare
49
+ * verbatim, no LLM turn. Absent or empty still delivers the request; the
50
+ * supplier just has nothing to triage on.
51
+ */
52
+ match?: string;
47
53
  };
48
54
  /**
49
55
  * A trust link's agreement, as every caller sees it.
@@ -124,8 +130,12 @@ export declare function respondToAgreement(agreementId: string, action: 'approve
124
130
  export interface PendingApprovalFacts {
125
131
  /** Party ids with a pending entry on the approvals ledger. */
126
132
  pendingPartyIds: readonly string[];
127
- /** The direct responder slot, when the proposal names one. */
128
- proposedTo?: string | null;
133
+ /**
134
+ * The direct responder slot, when the proposal names one. A side names up to
135
+ * two ids — the accountable principal and the delegate acting for it — and
136
+ * either may be the one holding the decision.
137
+ */
138
+ proposedToIds?: readonly string[];
129
139
  }
130
140
  /**
131
141
  * Which of `candidateIds` holds the pending approval slot, first match wins —
@@ -182,17 +192,33 @@ export declare function getMyAgreements(filters: GetMyAgreementsFilters | undefi
182
192
  /** One agreement, shaped: a link comes back as its summary. */
183
193
  export declare function getAgreement(agreementId: string, creds: Creds): Promise<Agreement | null>;
184
194
  export interface CreateLinkBody {
185
- /** Target agent for a direct link. Omit for an open, claimable invite. */
186
- targetAgentId?: string;
195
+ /**
196
+ * Who to connect with: an email address, or the id of an agent that answers
197
+ * to them. Either way the link is with the PERSON — an id is only a way to
198
+ * find its owner, and an agent that answers to an org is refused. Omit for an
199
+ * open invite you share yourself.
200
+ */
201
+ to?: string;
187
202
  /** Message shown to whoever is asked to approve or claim it. */
188
203
  description?: string;
189
204
  /** Open invites only: how many people may claim this one link. Default 1. */
190
205
  maxClaims?: number;
191
- /** Which of the caller's own agents stands on their side of the link. */
192
- asAgentId?: string;
193
206
  }
194
207
  /**
195
- * Create a trust link — a direct proposal to one agent, or an open invite.
208
+ * The route's one answer, whatever was in `to`.
209
+ *
210
+ * Deliberately carries no agreement. Returning the row for an id target and a
211
+ * bare "sent" for an address made one of the two an existence oracle over the
212
+ * user table, so both say the same thing now: it went. `shareUrl` is null
213
+ * exactly when `to` was named, which the caller already knows.
214
+ */
215
+ export interface CreateLinkResult {
216
+ ok: boolean;
217
+ status: 'sent';
218
+ shareUrl: string | null;
219
+ }
220
+ /**
221
+ * Connect with someone — by email, by agent id, or with a shareable invite.
196
222
  *
197
223
  * This posts to `POST /agreements/links`. It used to post to `POST /agreements`,
198
224
  * which also created work agreements inline and skipped the gates the proposal
@@ -200,10 +226,7 @@ export interface CreateLinkBody {
200
226
  * (`POST /agreements/proposals`) and the marketplace claim routes; this rail
201
227
  * carries links and nothing else.
202
228
  */
203
- export declare function createLink(body: CreateLinkBody, creds: Creds): Promise<{
204
- ok: boolean;
205
- agreement: Agreement;
206
- }>;
229
+ export declare function createLink(body: CreateLinkBody, creds: Creds): Promise<CreateLinkResult>;
207
230
  export declare function revokeAgreement(agreementId: string, creds: Creds): Promise<{
208
231
  ok: boolean;
209
232
  agreement: Agreement;
@@ -225,19 +248,33 @@ export type ClaimedKind = 'link' | 'offer' | 'request' | 'hand-off';
225
248
  *
226
249
  * POST /agreements/:id/claim — canonical; no partyId in body.
227
250
  */
228
- export declare function claimAgreement(agreementId: string, creds: Creds): Promise<{
251
+ /**
252
+ * What a claimer can say about WHY it is claiming.
253
+ *
254
+ * A claim binds a party to terms somebody else posted, so the only thing there
255
+ * is to declare is the authority behind it: the job the claiming agent is
256
+ * already doing. Nothing here changes the row — the lineage of a posted
257
+ * agreement belongs to whoever posted it.
258
+ */
259
+ export interface ClaimOptions {
260
+ /**
261
+ * The active agreement whose work this claim is part of. An agent acting
262
+ * inside a job its human approved does not need a second consent for the
263
+ * engagements that job requires, but it has to NAME the job — the server
264
+ * verifies the claim against the acting agent and never guesses one.
265
+ */
266
+ mandateAgreementId?: string;
267
+ }
268
+ export declare function claimAgreement(agreementId: string, creds: Creds, opts?: ClaimOptions): Promise<{
229
269
  ok: boolean;
230
270
  agreement: Agreement;
231
271
  kind?: ClaimedKind;
232
272
  }>;
233
273
  /**
234
- * Link types a caller may ASK for — the server's `CALLER_LINK_TYPES`, not its
235
- * full `LINK_TYPES`. `space` is deliberately absent: an agreement space gets
236
- * exactly one system-minted room, and letting a request name that type would
237
- * burn the root's single slot on an unrelated chat. Shorter than the server
238
- * union on purpose, which is why this list carries the reason with it.
274
+ * Link types a caller may ask for. `origin` and `delegation` are workflow-owned;
275
+ * callers may only pin an agreement into a chat as a reference (`mention`).
239
276
  */
240
- export declare const CHAT_LINK_TYPES: readonly ["origin", "mention", "delegation", "join"];
277
+ export declare const CHAT_LINK_TYPES: readonly ["mention"];
241
278
  export type ChatLinkType = (typeof CHAT_LINK_TYPES)[number];
242
279
  export declare function linkAgreementToChat(agreementId: string, chatId: string, linkType: ChatLinkType | undefined, creds: Creds): Promise<unknown | null>;
243
280
  export declare function getChatsForAgreement(agreementId: string, creds: Creds): Promise<unknown[]>;
@@ -245,10 +282,6 @@ export declare const ARTIFACT_LINK_TYPES: readonly ["produced", "referenced"];
245
282
  export type ArtifactLinkType = (typeof ARTIFACT_LINK_TYPES)[number];
246
283
  export declare function linkArtifactToAgreement(agreementId: string, artifactId: string, linkType: ArtifactLinkType | undefined, creds: Creds): Promise<unknown | null>;
247
284
  export declare function getArtifactsForAgreement(agreementId: string, creds: Creds): Promise<unknown[]>;
248
- export declare const AGREEMENT_USER_ROLES: readonly ["payer", "provider", "participant", "observer"];
249
- export type UserRole = (typeof AGREEMENT_USER_ROLES)[number];
250
- export declare function linkUserToAgreement(agreementId: string, userId: string, role: UserRole, creds: Creds): Promise<unknown | null>;
251
- export declare function getUsersForAgreement(agreementId: string, creds: Creds): Promise<unknown[]>;
252
285
  export declare class AgreementClient {
253
286
  private creds;
254
287
  /**
@@ -267,10 +300,7 @@ export declare class AgreementClient {
267
300
  list(filters?: ListAgreementsFilters): Promise<Agreement[]>;
268
301
  listMine(filters?: GetMyAgreementsFilters): Promise<Agreement[]>;
269
302
  get(id: string): Promise<Agreement | null>;
270
- createLink(data: CreateLinkBody): Promise<{
271
- ok: boolean;
272
- agreement: Agreement;
273
- }>;
303
+ createLink(data: CreateLinkBody): Promise<CreateLinkResult>;
274
304
  revoke(id: string): Promise<{
275
305
  ok: boolean;
276
306
  agreement: Agreement;
@@ -288,6 +318,4 @@ export declare class AgreementClient {
288
318
  listChats(id: string): Promise<unknown[]>;
289
319
  linkArtifact(id: string, artifactId: string, linkType?: ArtifactLinkType): Promise<unknown>;
290
320
  listArtifacts(id: string): Promise<unknown[]>;
291
- linkUser(id: string, userId: string, role: UserRole): Promise<unknown>;
292
- listUsers(id: string): Promise<unknown[]>;
293
321
  }
@@ -1,6 +1,6 @@
1
1
  import { runtimeLog } from '../shared/runtimeLog.js';
2
2
  import { getBackendUrl } from '../utils/urlUtils.js';
3
- import { OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, isBroadcastTarget } from '../types.js';
3
+ import { OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, isBroadcastTarget, partySideIds } from '../types.js';
4
4
  import { throwApiError } from '../shared/apiError.js';
5
5
  // Lazy: read at call time so a `configureApiClient` call that lands after this
6
6
  // module is imported still takes effect. Baking it at module-load time would
@@ -37,6 +37,10 @@ function assertCreds(creds, op) {
37
37
  * unchanged.
38
38
  */
39
39
  export function linkSummary(a) {
40
+ const side = (s) => ({
41
+ principal: s?.principal ?? null,
42
+ actor: s?.actor ?? null,
43
+ });
40
44
  return {
41
45
  agreementId: a.agreementId,
42
46
  // Kept deliberately: callers branch on this, and a summary that hides what
@@ -44,11 +48,11 @@ export function linkSummary(a) {
44
48
  engagementKind: a.engagementKind,
45
49
  status: a.status,
46
50
  proposalStatus: a.proposalStatus,
51
+ // No `payer`: a link carries no money, so the paying side is never occupied.
47
52
  parties: {
48
- creatorAgent: a.parties?.creatorAgent ?? null,
49
- providerAgent: a.parties?.providerAgent ?? null,
50
- creator: a.parties?.creator ?? null,
51
- proposedTo: a.parties?.proposedTo ?? null,
53
+ creator: side(a.parties?.creator),
54
+ provider: side(a.parties?.provider),
55
+ proposedTo: side(a.parties?.proposedTo),
52
56
  },
53
57
  ...(a.description ? { description: a.description } : {}),
54
58
  // Seat bookkeeping on an open invite — link state, not commerce.
@@ -169,8 +173,8 @@ export async function respondToAgreement(agreementId, action, creds, opts = {})
169
173
  ownerUserId: opts.ownerUserId,
170
174
  agentId: creds.agentId,
171
175
  });
172
- const proposedTo = agreement.parties?.proposedTo;
173
- if (isBroadcastTarget(proposedTo) || isBroadcastTarget(agreement.parties?.payer)) {
176
+ const proposedTo = agreement.parties?.proposedTo?.principal;
177
+ if (isBroadcastTarget(proposedTo) || isBroadcastTarget(agreement.parties?.payer?.principal)) {
174
178
  // respond is approve/reject on DIRECT proposals only. An open
175
179
  // broadcast (request, standing offer, link invite) has no per-recipient
176
180
  // approval slot — a BYSTANDER can neither approve nor reject it
@@ -206,11 +210,12 @@ export function resolvePendingApprovalPartyId(facts, candidateIds) {
206
210
  }
207
211
  // A row with no pending ledger entries is a legacy one (approvals[] predates
208
212
  //), and there the named responder slot IS the decision.
209
- const proposedTo = facts.proposedTo ?? null;
210
- if (proposedTo &&
211
- actorIds.includes(proposedTo) &&
212
- facts.pendingPartyIds.length === 0) {
213
- return proposedTo;
213
+ if (facts.pendingPartyIds.length === 0) {
214
+ const responderIds = facts.proposedToIds ?? [];
215
+ for (const id of actorIds) {
216
+ if (responderIds.includes(id))
217
+ return id;
218
+ }
214
219
  }
215
220
  return null;
216
221
  }
@@ -223,7 +228,7 @@ export function resolveMyPendingApprovalPartyId(agreement, opts) {
223
228
  pendingPartyIds: (agreement.approvals ?? [])
224
229
  .filter((a) => a.status === 'pending' || a.status === 'PENDING')
225
230
  .map((a) => a.partyId),
226
- proposedTo: agreement.parties?.proposedTo ?? null,
231
+ proposedToIds: partySideIds(agreement.parties?.proposedTo),
227
232
  }, [opts.agentId, opts.ownerUserId]);
228
233
  }
229
234
  /**
@@ -407,7 +412,7 @@ export async function getAgreement(agreementId, creds) {
407
412
  return agreement ? shapeAgreement(agreement) : null;
408
413
  }
409
414
  /**
410
- * Create a trust link — a direct proposal to one agent, or an open invite.
415
+ * Connect with someone — by email, by agent id, or with a shareable invite.
411
416
  *
412
417
  * This posts to `POST /agreements/links`. It used to post to `POST /agreements`,
413
418
  * which also created work agreements inline and skipped the gates the proposal
@@ -429,11 +434,15 @@ export async function createLink(body, creds) {
429
434
  throwApiError(res, responseBody, `Link creation failed: ${res.status} ${res.statusText}`);
430
435
  }
431
436
  const data = await res.json().catch(() => null);
432
- if (!data?.['agreement']) {
433
- throw new Error('Invalid response: expected { ok, agreement } from POST /agreements/links');
437
+ if (data?.['status'] !== 'sent') {
438
+ throw new Error('Invalid response: expected { ok, status: "sent", shareUrl } from POST /agreements/links');
434
439
  }
435
- const envelope = data;
436
- return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
440
+ const shareUrl = data['shareUrl'];
441
+ return {
442
+ ok: data['ok'] === true,
443
+ status: 'sent',
444
+ shareUrl: typeof shareUrl === 'string' ? shareUrl : null,
445
+ };
437
446
  }
438
447
  export async function revokeAgreement(agreementId, creds) {
439
448
  if (!agreementId)
@@ -477,20 +486,15 @@ export async function fulfillAgreement(agreementId, creds) {
477
486
  const envelope = data;
478
487
  return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
479
488
  }
480
- /**
481
- * Claim an open agreement. Three shapes are claimable:
482
- * - open link invite (`engagementKind: link`, proposedTo everyone)
483
- * - open broadcast request (proposedTo 'everyone', status open)
484
- * - org-scoped broadcast request (proposedTo 'org') — the server rejects the
485
- * claim with 403 if the caller is not a member of the agreement's orgId
486
- *
487
- * POST /agreements/:id/claim — canonical; no partyId in body.
488
- */
489
- export async function claimAgreement(agreementId, creds) {
489
+ export async function claimAgreement(agreementId, creds, opts = {}) {
490
490
  if (!agreementId)
491
491
  throw new Error('agreementId is required to claim an open agreement');
492
492
  assertCreds(creds, 'open agreement claim');
493
- const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}/claim`, { method: 'POST', headers: buildHeaders(creds) });
493
+ const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}/claim`, {
494
+ method: 'POST',
495
+ headers: buildHeaders(creds),
496
+ body: JSON.stringify(opts.mandateAgreementId ? { mandateAgreementId: opts.mandateAgreementId } : {}),
497
+ });
494
498
  if (!res.ok) {
495
499
  const body = await res.text().catch(() => '');
496
500
  throwApiError(res, body, `Open agreement claim failed: ${res.status} ${res.statusText}`);
@@ -510,18 +514,10 @@ export async function claimAgreement(agreementId, creds) {
510
514
  // Chat links
511
515
  // ---------------------------------------------------------------------------
512
516
  /**
513
- * Link types a caller may ASK for — the server's `CALLER_LINK_TYPES`, not its
514
- * full `LINK_TYPES`. `space` is deliberately absent: an agreement space gets
515
- * exactly one system-minted room, and letting a request name that type would
516
- * burn the root's single slot on an unrelated chat. Shorter than the server
517
- * union on purpose, which is why this list carries the reason with it.
517
+ * Link types a caller may ask for. `origin` and `delegation` are workflow-owned;
518
+ * callers may only pin an agreement into a chat as a reference (`mention`).
518
519
  */
519
- export const CHAT_LINK_TYPES = [
520
- 'origin',
521
- 'mention',
522
- 'delegation',
523
- 'join',
524
- ];
520
+ export const CHAT_LINK_TYPES = ['mention'];
525
521
  export async function linkAgreementToChat(agreementId, chatId, linkType = 'mention', creds) {
526
522
  if (!agreementId || !chatId)
527
523
  return null;
@@ -614,59 +610,6 @@ export async function getArtifactsForAgreement(agreementId, creds) {
614
610
  return [];
615
611
  }
616
612
  }
617
- // ---------------------------------------------------------------------------
618
- // User links
619
- // ---------------------------------------------------------------------------
620
- export const AGREEMENT_USER_ROLES = [
621
- 'payer',
622
- 'provider',
623
- 'participant',
624
- 'observer',
625
- ];
626
- export async function linkUserToAgreement(agreementId, userId, role, creds) {
627
- if (!agreementId || !userId || !role)
628
- return null;
629
- assertCreds(creds, 'link user to agreement');
630
- try {
631
- const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/users`, {
632
- method: 'POST',
633
- headers: buildHeaders(creds),
634
- body: JSON.stringify({ userId, role }),
635
- });
636
- if (!res.ok) {
637
- const body = await res.text().catch(() => '');
638
- runtimeLog.warn('AgreementClient', `⚠️ Link user failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
639
- return null;
640
- }
641
- return await res.json().catch(() => null);
642
- }
643
- catch (e) {
644
- runtimeLog.warn('AgreementClient', `⚠️ Link user failed: ${e.message}`);
645
- return null;
646
- }
647
- }
648
- export async function getUsersForAgreement(agreementId, creds) {
649
- if (!agreementId)
650
- return [];
651
- assertCreds(creds, 'get users for agreement');
652
- try {
653
- const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/users`, {
654
- method: 'GET',
655
- headers: buildHeaders(creds),
656
- });
657
- if (!res.ok) {
658
- const body = await res.text().catch(() => '');
659
- runtimeLog.warn('AgreementClient', `⚠️ Get users failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
660
- return [];
661
- }
662
- const data = await res.json().catch(() => null);
663
- return Array.isArray(data?.['users']) ? data['users'] : [];
664
- }
665
- catch (e) {
666
- runtimeLog.warn('AgreementClient', `⚠️ Get users failed: ${e.message}`);
667
- return [];
668
- }
669
- }
670
613
  export class AgreementClient {
671
614
  creds;
672
615
  /**
@@ -699,6 +642,4 @@ export class AgreementClient {
699
642
  listChats(id) { return getChatsForAgreement(id, this.creds); }
700
643
  linkArtifact(id, artifactId, linkType) { return linkArtifactToAgreement(id, artifactId, linkType ?? 'produced', this.creds); }
701
644
  listArtifacts(id) { return getArtifactsForAgreement(id, this.creds); }
702
- linkUser(id, userId, role) { return linkUserToAgreement(id, userId, role, this.creds); }
703
- listUsers(id) { return getUsersForAgreement(id, this.creds); }
704
645
  }
@@ -1,10 +1,16 @@
1
+ import { CONTEXT_GRANT_SCOPE_KINDS } from './grants.js';
1
2
  import type { GrantAccessKind, GrantHolderKind, GrantView } from './grants.js';
2
3
  /**
3
- * added `artifact` — the narrowest context scope: one specific artifact,
4
- * shared without sharing any chat or agreement it sits in. Still the context
5
- * rail, not a new grant primitive.
4
+ * The context rail's scopes. `artifact` is the narrowest — one specific
5
+ * artifact, shared without sharing any chat or agreement it sits in. `task` is
6
+ * a branch of a work graph: the named task and everything under it. Both are
7
+ * still the context rail, not new grant primitives.
8
+ *
9
+ * Derived from {@link CONTEXT_GRANT_SCOPE_KINDS} rather than written out again:
10
+ * a hand-copied subset still typechecks against the union, which is how this
11
+ * list sat a scope kind behind the rail.
6
12
  */
7
- export type ContextGrantScopeKind = 'chat' | 'agreement' | 'org' | 'artifact';
13
+ export type ContextGrantScopeKind = (typeof CONTEXT_GRANT_SCOPE_KINDS)[number];
8
14
  export type ContextTemporal = 'from-now' | 'from-start';
9
15
  export interface ContextGrantScope {
10
16
  kind: ContextGrantScopeKind;
@@ -1,5 +1,6 @@
1
1
  import { getBackendUrl } from '../utils/urlUtils.js';
2
2
  import { pollSurfaceError } from '../shared/rateLimit.js';
3
+ import { INBOX_HOST_CLAIMANT_HEADER, instanceIdentity, } from '../instanceIdentity.js';
3
4
  /**
4
5
  * The doorbell, not the door: references addressed to this agent
5
6
  * since its last ack — never content. Flow: inbox → read → act → ack
@@ -24,6 +25,9 @@ export class InboxClient {
24
25
  const headers = {
25
26
  Authorization: `Bearer ${this.operatorKey}`,
26
27
  'Content-Type': 'application/json',
28
+ // This process, not the API. Two fleets of the same agent share
29
+ // one backend task; without this they both wake.
30
+ [INBOX_HOST_CLAIMANT_HEADER]: instanceIdentity(),
27
31
  };
28
32
  if (this.agentId)
29
33
  headers['X-Agent-Id'] = this.agentId;
@@ -23,22 +23,6 @@ export interface PullOffersOptions {
23
23
  since?: string;
24
24
  }
25
25
  export declare function pullOffers(options: PullOffersOptions | undefined, creds: Creds): Promise<Agreement[]>;
26
- export declare function claimOffer(agreementId: string, creds: Creds): Promise<Agreement>;
27
- export interface PublishRequestPayload {
28
- /** See PublishOfferPayload.billing. */
29
- billing?: 'total' | 'per_task';
30
- description: string;
31
- chatId?: string;
32
- payerId?: string;
33
- price?: number;
34
- lifecycle?: string;
35
- expiresAt?: string;
36
- maxExecutions?: number;
37
- agreementDescription?: string;
38
- /** Broadcast audience: 'everyone' (default, fully public) or 'org' (members of your active org only). */
39
- audience?: BroadcastAudience;
40
- }
41
- export declare function publishRequest(payload: PublishRequestPayload, creds: Creds): Promise<Agreement>;
42
26
  export interface PullRequestsOptions {
43
27
  limit?: number;
44
28
  since?: string;
@@ -53,7 +37,5 @@ export declare class MarketplaceClient {
53
37
  constructor(operatorKey: string, agentId?: string);
54
38
  publishOffer(payload: PublishOfferPayload): Promise<Agreement>;
55
39
  pullOffers(options?: PullOffersOptions): Promise<Agreement[]>;
56
- claimOffer(agreementId: string): Promise<Agreement>;
57
- publishRequest(payload: PublishRequestPayload): Promise<Agreement>;
58
40
  pullRequests(options?: PullRequestsOptions): Promise<Agreement[]>;
59
41
  }
@@ -52,40 +52,6 @@ export async function pullOffers(options, creds) {
52
52
  const data = await res.json().catch(() => null);
53
53
  return data?.['offers'] ?? [];
54
54
  }
55
- export async function claimOffer(agreementId, creds) {
56
- if (!agreementId)
57
- throw new Error('agreementId is required');
58
- assertCreds(creds, 'marketplace offer claim');
59
- const res = await fetch(`${getMarketplaceBaseUrl()}/offers/claim`, {
60
- method: 'POST',
61
- headers: buildHeaders(creds),
62
- body: JSON.stringify({ agreementId }),
63
- });
64
- if (!res.ok) {
65
- const body = await res.text().catch(() => '');
66
- throwApiError(res, body, `Marketplace offer claim failed: ${res.status}`);
67
- }
68
- const data = await res.json().catch(() => null);
69
- if (!data?.['ok'])
70
- throw new Error(data?.['error'] || 'Claim failed');
71
- return shapeAgreement(data['offer']);
72
- }
73
- export async function publishRequest(payload, creds) {
74
- assertCreds(creds, 'request publish');
75
- const res = await fetch(`${getMarketplaceBaseUrl()}/requests/publish`, {
76
- method: 'POST',
77
- headers: buildHeaders(creds),
78
- body: JSON.stringify(payload || {}),
79
- });
80
- if (!res.ok) {
81
- const body = await res.text().catch(() => '');
82
- throwApiError(res, body, `Request publish failed: ${res.status}`);
83
- }
84
- const data = await res.json().catch(() => null);
85
- if (!data?.['agreement'])
86
- throw new Error('Request publish returned no agreement');
87
- return shapeAgreement(data['agreement']);
88
- }
89
55
  export async function pullRequests(options, creds) {
90
56
  assertCreds(creds, 'request pull');
91
57
  const params = new URLSearchParams();
@@ -120,7 +86,5 @@ export class MarketplaceClient {
120
86
  }
121
87
  publishOffer(payload) { return publishOffer(payload, this.creds); }
122
88
  pullOffers(options) { return pullOffers(options, this.creds); }
123
- claimOffer(agreementId) { return claimOffer(agreementId, this.creds); }
124
- publishRequest(payload) { return publishRequest(payload, this.creds); }
125
89
  pullRequests(options) { return pullRequests(options, this.creds); }
126
90
  }
@@ -37,10 +37,24 @@ export interface CreateTaskData {
37
37
  planReviewTiming?: PlanReviewTiming;
38
38
  requireMidWorkPlanAck?: boolean;
39
39
  idempotencyKey?: string;
40
- /** Explicit delegation target — must be a party to the agreement; validated server-side. */
40
+ /**
41
+ * Explicit delegation target — must hold a grant on the agreement, so work
42
+ * can only be handed to someone that contract already reaches; validated
43
+ * server-side.
44
+ */
41
45
  assigneeId?: string;
42
46
  /** prior-step artifact handles consumed as structured input links. */
43
47
  inputArtifactIds?: string[];
48
+ /**
49
+ * Tasks this one waits for. The task is created immediately, but its assignee
50
+ * is not woken and cannot start it until every task named here is terminal.
51
+ * Declaring an edge needs read reach on the task you name.
52
+ *
53
+ * Declared rather than enacted: state the join up front and the platform
54
+ * wakes the joiner once, instead of a live agent polling for it and holding
55
+ * the shape in its own control flow where nothing else can see it.
56
+ */
57
+ waitsOn?: string[];
44
58
  }
45
59
  export declare function createTask(taskData: CreateTaskData, creds: Creds): Promise<Task>;
46
60
  export declare function getTask(taskId: string, creds: Creds): Promise<Task>;
@@ -1,4 +1,5 @@
1
1
  import { getBackendUrl } from '../utils/urlUtils.js';
2
+ import { partyActorIds } from '../types.js';
2
3
  import { throwApiError } from '../shared/apiError.js';
3
4
  function getTaskBaseUrl() { return `${getBackendUrl()}/tasks`; }
4
5
  function buildHeaders(creds) {
@@ -159,13 +160,7 @@ async function getActiveTasksForAgentViaPartyAgreements(agentId, creds) {
159
160
  const { listAgreements } = await import('./AgreementClient.js');
160
161
  const agreements = await listAgreements({ status: 'active' }, creds);
161
162
  const partyIds = agreements
162
- .filter((a) => {
163
- const p = a.parties ?? {};
164
- return (p.provider === agentId ||
165
- p.providerAgent === agentId ||
166
- p.creator === agentId ||
167
- p.payer === agentId);
168
- })
163
+ .filter((a) => partyActorIds(a.parties).includes(agentId))
169
164
  .map((a) => a.agreementId)
170
165
  .filter(Boolean);
171
166
  if (partyIds.length === 0)
@@ -1,4 +1,4 @@
1
- import { type ClaimedKind, type ProposeTerms } from './AgreementClient.js';
1
+ import { type ClaimedKind, type ClaimOptions, type ProposeTerms } from './AgreementClient.js';
2
2
  import { type Agreement, type Creds, type EngagementKind } from '../types.js';
3
3
  /**
4
4
  * one propose grammar. Direct, broadcast (request and standing
@@ -6,7 +6,6 @@ import { type Agreement, type Creds, type EngagementKind } from '../types.js';
6
6
  * single propose tool instead of dedicated publish/request tools.
7
7
  *
8
8
  * Routing:
9
- * - engagementKind 'link' → POST /agreements (link proposal / open invite)
10
9
  * - proposedTo 'everyone' | 'org', providerId = self → seller-broadcast standing offer
11
10
  * - proposedTo 'everyone' | 'org', no providerId → buyer-broadcast request
12
11
  * - anything else → direct proposal
@@ -17,7 +16,7 @@ export interface UnifiedProposeInput extends ProposeTerms {
17
16
  chatId?: string;
18
17
  engagementKind?: EngagementKind;
19
18
  }
20
- export type ProposeShape = 'direct' | 'request' | 'offer' | 'link';
19
+ export type ProposeShape = 'direct' | 'request' | 'offer';
21
20
  export declare function proposeUnified(input: UnifiedProposeInput, creds: Creds): Promise<{
22
21
  agreement: Agreement;
23
22
  shape: ProposeShape;
@@ -36,7 +35,7 @@ export declare function proposeUnified(input: UnifiedProposeInput, creds: Creds)
36
35
  * `kind` arrives on the claim response — the route that did the routing reports
37
36
  * which broadcast kind this turned out to be.
38
37
  */
39
- export declare function claimOpenAgreement(agreementId: string, creds: Creds): Promise<{
38
+ export declare function claimOpenAgreement(agreementId: string, creds: Creds, opts?: ClaimOptions): Promise<{
40
39
  agreement: Agreement;
41
40
  kind: ClaimedKind;
42
41
  }>;