@ziggs-ai/api-client 0.38.0 → 0.39.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +14 -36
  3. package/dist/capabilities/agreementVerbs.js +13 -21
  4. package/dist/capabilities/agreements.d.ts +2 -3
  5. package/dist/capabilities/agreements.js +8 -43
  6. package/dist/capabilities/artifacts.d.ts +6 -0
  7. package/dist/capabilities/artifacts.js +84 -43
  8. package/dist/capabilities/connections.js +10 -7
  9. package/dist/capabilities/context.d.ts +1 -1
  10. package/dist/capabilities/context.js +52 -45
  11. package/dist/capabilities/discovery.js +5 -5
  12. package/dist/capabilities/everyday.d.ts +1 -1
  13. package/dist/capabilities/everyday.js +1 -2
  14. package/dist/capabilities/index.d.ts +3 -4
  15. package/dist/capabilities/index.js +3 -4
  16. package/dist/capabilities/introductions.d.ts +1 -0
  17. package/dist/capabilities/introductions.js +64 -17
  18. package/dist/capabilities/links.d.ts +1 -9
  19. package/dist/capabilities/links.js +40 -39
  20. package/dist/capabilities/mcpBriefs.js +41 -34
  21. package/dist/capabilities/messaging.d.ts +9 -0
  22. package/dist/capabilities/messaging.js +11 -0
  23. package/dist/capabilities/nextCall.js +8 -17
  24. package/dist/capabilities/sources.d.ts +2 -3
  25. package/dist/capabilities/sources.js +1 -2
  26. package/dist/capabilities/tasks.d.ts +1 -0
  27. package/dist/capabilities/tasks.js +43 -2
  28. package/dist/capabilities/toolLane.js +2 -2
  29. package/dist/engagementGuide.d.ts +2 -2
  30. package/dist/engagementGuide.js +6 -6
  31. package/dist/handles.d.ts +1 -1
  32. package/dist/handles.js +2 -2
  33. package/dist/http/AgreementClient.d.ts +25 -63
  34. package/dist/http/AgreementClient.js +14 -65
  35. package/dist/http/ArtifactsClient.d.ts +31 -19
  36. package/dist/http/ArtifactsClient.js +43 -62
  37. package/dist/http/ConnectionsClient.d.ts +6 -7
  38. package/dist/http/ConnectionsClient.js +5 -3
  39. package/dist/http/ContextDiscoveryClient.d.ts +2 -2
  40. package/dist/http/ContextGrantsClient.d.ts +9 -15
  41. package/dist/http/ContextGrantsClient.js +3 -5
  42. package/dist/http/ContextOpenClient.d.ts +5 -6
  43. package/dist/http/ContextOpenClient.js +11 -4
  44. package/dist/http/ContextReadClient.d.ts +23 -27
  45. package/dist/http/ContextReadClient.js +24 -23
  46. package/dist/http/ExecutionContextClient.d.ts +5 -1
  47. package/dist/http/ExecutionContextClient.js +48 -1
  48. package/dist/http/IntroductionsClient.d.ts +33 -4
  49. package/dist/http/IntroductionsClient.js +6 -2
  50. package/dist/http/MessageRecordsClient.d.ts +36 -0
  51. package/dist/http/MessageRecordsClient.js +30 -0
  52. package/dist/http/SessionsClient.d.ts +5 -5
  53. package/dist/http/SessionsClient.js +6 -6
  54. package/dist/http/TaskClient.d.ts +35 -6
  55. package/dist/http/TaskClient.js +38 -36
  56. package/dist/http/agreementFlows.d.ts +2 -3
  57. package/dist/http/agreementFlows.js +5 -5
  58. package/dist/http/index.d.ts +3 -5
  59. package/dist/http/index.js +3 -4
  60. package/dist/http/operatorHeaders.d.ts +3 -0
  61. package/dist/http/operatorHeaders.js +14 -5
  62. package/dist/inboxAck.d.ts +2 -2
  63. package/dist/inboxAck.js +2 -2
  64. package/dist/index.d.ts +4 -4
  65. package/dist/index.js +2 -2
  66. package/dist/types.d.ts +25 -55
  67. package/package.json +2 -2
  68. package/dist/capabilities/chat.d.ts +0 -43
  69. package/dist/capabilities/chat.js +0 -196
  70. package/dist/capabilities/chatListFields.d.ts +0 -14
  71. package/dist/capabilities/chatListFields.js +0 -61
  72. package/dist/http/ChatClient.d.ts +0 -187
  73. package/dist/http/ChatClient.js +0 -219
  74. package/dist/http/MessagesClient.d.ts +0 -40
  75. package/dist/http/MessagesClient.js +0 -81
@@ -161,13 +161,15 @@ export class ConnectionsClient {
161
161
  /**
162
162
  * agent-initiated MCP connection request: ask the principal to
163
163
  * connect a remote MCP server and grant this agent the listed tools. Opens a
164
- * connection-consent agreement in the working chat; on approval the
164
+ * connection-consent agreement for explicit work/message context; on approval the
165
165
  * server is connected (if needed) and the agent is granted the tools.
166
166
  */
167
- async requestMcpConnection({ serverUrl, tools, reason, chatId, recipientId, onBehalfOfUserId, }) {
167
+ async requestMcpConnection({ serverUrl, tools, reason, context, recipientId, onBehalfOfUserId, }) {
168
168
  if (!serverUrl)
169
169
  throw new Error("requestMcpConnection: serverUrl is required");
170
- return (await this._request("POST", "/connections/mcp/requests", { serverUrl, tools, reason, chatId, ...(recipientId ? { recipientId } : {}) }, onBehalfOfUserId
170
+ if (!context || !["message", "task", "agreement"].includes(context.kind) || !context.id)
171
+ throw new Error("requestMcpConnection: context is required");
172
+ return (await this._request("POST", "/connections/mcp/requests", { serverUrl, tools, reason, context, ...(recipientId ? { recipientId } : {}) }, onBehalfOfUserId
171
173
  ? { "X-On-Behalf-Of-User": onBehalfOfUserId }
172
174
  : undefined));
173
175
  }
@@ -4,10 +4,10 @@
4
4
  * provider name only (never tokens/account labels).
5
5
  */
6
6
  export interface DiscoverableItem {
7
- type: 'chat' | 'agreement' | 'connection';
7
+ type: 'address' | 'agreement' | 'connection';
8
8
  label: string;
9
9
  scopeRef: {
10
- kind: 'chat' | 'agreement' | 'org' | 'connection';
10
+ kind: 'address' | 'agreement' | 'org' | 'connection';
11
11
  id: string;
12
12
  };
13
13
  orgId: string;
@@ -69,18 +69,16 @@ export interface ReachEntry {
69
69
  label: string;
70
70
  }
71
71
  /**
72
- * reach expansion — the chat/agreement ids a grant you hold actually
72
+ * Reach expansion — the agreement ids a grant you hold actually
73
73
  * covers, so an org/agreement-scoped grant becomes a concrete list you can
74
- * `context_read` through (via=chat:<id> / agreement:<id>). Ids + labels only,
75
- * never content. Org scope is capped; `truncatedChats`/`truncatedAgreements`
76
- * report how many were left off, never silently dropped.
74
+ * inspect using the retained resource operations. Ids + labels only,
75
+ * never content. Org scope is capped; `truncatedAgreements`
76
+ * reports how many were left off.
77
77
  */
78
78
  export interface GrantReachResult {
79
79
  grantId: string;
80
80
  scope: ContextGrantScope;
81
- chats: ReachEntry[];
82
81
  agreements: ReachEntry[];
83
- truncatedChats: number;
84
82
  truncatedAgreements: number;
85
83
  }
86
84
  /**
@@ -98,20 +96,20 @@ export declare class ContextGrantsClient {
98
96
  issueGrant(input: IssueContextGrantInput): Promise<GrantView>;
99
97
  /** Request-only endpoint: a successful call must still have granted no access. */
100
98
  requestAccess(input: {
101
- scopeKind: 'artifact' | 'chat' | 'agreement' | 'task' | 'person';
99
+ scopeKind: 'message' | 'artifact' | 'address' | 'agreement' | 'task' | 'person';
102
100
  scopeId?: string;
103
101
  agreementId?: string;
104
102
  reason: string;
105
103
  permission?: 'read' | 'reply' | 'work' | 'list';
106
104
  temporal?: 'from-now' | 'from-start';
107
105
  durationHours?: number;
108
- chatId?: string;
109
106
  ownerId?: string;
110
107
  /** Who to ask, from the people the server offers this wake. */
111
108
  recipientId?: string;
112
109
  /** Artifact only: who should read it, if not only the caller. One grant each. */
113
110
  holders?: AccessReader[];
114
111
  contributionTo?: import('@ziggs-ai/contracts').ContributionTarget;
112
+ releaseSource?: import('@ziggs-ai/contracts').ContributionSource;
115
113
  }, opts?: {
116
114
  idempotencyKey?: string;
117
115
  laneId?: string;
@@ -120,9 +118,9 @@ export declare class ContextGrantsClient {
120
118
  idempotencyKey?: string;
121
119
  }): Promise<DelegateContextGrantResult>;
122
120
  /**
123
- * expand a grant you hold into the chat/agreement ids inside its
121
+ * Expand a grant you hold into the agreement ids inside its
124
122
  * scope. A grant is a fence, not a listing: discovery says "you hold
125
- * org:acme", this says which chats/agreements that covers. Holder-only,
123
+ * org:acme", this says which agreements that covers. Holder-only,
126
124
  * labels-only, grant-fenced server-side.
127
125
  */
128
126
  getReach(grantId: string): Promise<GrantReachResult>;
@@ -139,7 +137,6 @@ export declare class ContextGrantsClient {
139
137
  shareArtifact(artifactId: string, input: {
140
138
  holderId: string;
141
139
  expiresAt?: string | null;
142
- chatId?: string;
143
140
  }, opts?: {
144
141
  idempotencyKey?: string;
145
142
  }): Promise<DelegateContextGrantResult>;
@@ -181,11 +178,8 @@ export interface RequestSentTo {
181
178
  role: 'customer' | 'operator';
182
179
  };
183
180
  approveIn?: {
184
- kind: 'chat';
185
- chatId: string;
186
- } | {
187
181
  kind: 'inbox';
188
182
  };
189
- /** e.g. "Sent to Ido. Ido approves it in this room." */
183
+ /** Human-readable approval destination. */
190
184
  message?: string;
191
185
  }
@@ -114,9 +114,9 @@ export class ContextGrantsClient {
114
114
  return parseGrantOrPending(body, 'POST /context/grants/:id/delegate');
115
115
  }
116
116
  /**
117
- * expand a grant you hold into the chat/agreement ids inside its
117
+ * Expand a grant you hold into the agreement ids inside its
118
118
  * scope. A grant is a fence, not a listing: discovery says "you hold
119
- * org:acme", this says which chats/agreements that covers. Holder-only,
119
+ * org:acme", this says which agreements that covers. Holder-only,
120
120
  * labels-only, grant-fenced server-side.
121
121
  */
122
122
  async getReach(grantId) {
@@ -127,14 +127,12 @@ export class ContextGrantsClient {
127
127
  }
128
128
  const parsed = JSON.parse(body);
129
129
  if (!parsed.scope) {
130
- throw new Error('Invalid response: expected { scope, chats, agreements } from GET /context/grants/:id/reach');
130
+ throw new Error('Invalid response: expected { scope, agreements } from GET /context/grants/:id/reach');
131
131
  }
132
132
  return {
133
133
  grantId: parsed.grantId ?? grantId,
134
134
  scope: parsed.scope,
135
- chats: parsed.chats ?? [],
136
135
  agreements: parsed.agreements ?? [],
137
- truncatedChats: parsed.truncatedChats ?? 0,
138
136
  truncatedAgreements: parsed.truncatedAgreements ?? 0,
139
137
  };
140
138
  }
@@ -1,4 +1,4 @@
1
- export declare const CONTEXT_OPEN_KINDS: readonly ["artifact", "chat", "task", "agreement"];
1
+ export declare const CONTEXT_OPEN_KINDS: readonly ["artifact", "task", "agreement"];
2
2
  export type ContextOpenKind = (typeof CONTEXT_OPEN_KINDS)[number];
3
3
  export type ContextOpenOutcome = 'ok' | 'unreadable' | 'hidden';
4
4
  export interface ContextOpenRef {
@@ -6,10 +6,9 @@ export interface ContextOpenRef {
6
6
  id: string;
7
7
  }
8
8
  export interface ContextOpenBody {
9
- /** A handle (T…/A…/C…/F…), a Ziggs URL holding one, or an older id. */
9
+ /** A handle (T…/A…/F…), a Ziggs URL holding one, or an older id. */
10
10
  ref?: string;
11
11
  artifactId?: string;
12
- chatId?: string;
13
12
  taskId?: string;
14
13
  agreementId?: string;
15
14
  cursor?: string;
@@ -50,10 +49,10 @@ export interface ContextOpenAccess {
50
49
  kind: 'owner_decision';
51
50
  summary: string;
52
51
  request?: {
53
- scopeKind: 'chat' | 'agreement' | 'task';
52
+ scopeKind: 'agreement' | 'task';
54
53
  scopeId: string;
55
- permission: 'read' | 'reply';
56
- temporal: 'from-now' | 'from-start';
54
+ permission: 'read';
55
+ temporal: 'from-start';
57
56
  durationHours: number;
58
57
  };
59
58
  };
@@ -4,7 +4,6 @@ import { pollSurfaceError } from '../shared/rateLimit.js';
4
4
  import { buildOperatorHeaders } from './operatorHeaders.js';
5
5
  export const CONTEXT_OPEN_KINDS = [
6
6
  'artifact',
7
- 'chat',
8
7
  'task',
9
8
  'agreement',
10
9
  ];
@@ -43,21 +42,29 @@ export class ContextOpenClient {
43
42
  if (!res.ok) {
44
43
  throw pollSurfaceError(`ContextOpenClient ${path}`, res, text);
45
44
  }
46
- return JSON.parse(text);
45
+ const result = JSON.parse(text);
46
+ if (result?.ref?.kind === 'address') {
47
+ throw Object.assign(new Error('Room references are retired; select an exact artifact, task or agreement'), { code: 'INVALID_INPUT' });
48
+ }
49
+ return result;
47
50
  }
48
51
  }
49
52
  export function normalizeContextOpenBody(args) {
50
53
  // A mistyped handle is refused here, before any call: its check letter
51
54
  // fails. Any other id goes to the server, which knows older shapes.
52
55
  const invalid = (message) => Object.assign(new Error(message), { code: 'INVALID_INPUT' });
53
- const keys = ['ref', 'artifactId', 'chatId', 'taskId', 'agreementId'].filter(key => args[key] !== undefined);
56
+ if ('addressId' in args)
57
+ throw invalid('addressId is retired; select an exact artifact, task or agreement');
58
+ const keys = ['ref', 'artifactId', 'taskId', 'agreementId'].filter(key => args[key] !== undefined);
54
59
  if (keys.length !== 1)
55
- throw invalid('Pass exactly one resource reference: ref, artifactId, chatId, taskId, or agreementId');
60
+ throw invalid('Pass exactly one resource reference: ref, artifactId, taskId, or agreementId');
56
61
  const key = keys[0];
57
62
  const raw = typeof args[key] === 'string' ? args[key].trim() : '';
58
63
  if (!raw)
59
64
  throw invalid(`${key} must be a non-empty resource reference`);
60
65
  const ref = parseRef(raw);
66
+ if (ref?.kind === 'address' || /^(?:address|chat):/.test(raw))
67
+ throw invalid('Room references are retired; select an exact artifact, task or agreement');
61
68
  if (!ref && looksLikeHandle(raw))
62
69
  throw invalid(`${raw} is not a handle: its check letter does not match. Check it for a typo`);
63
70
  const body = { [key]: ref?.id ?? raw };
@@ -1,23 +1,23 @@
1
- export declare const CONTEXT_READ_TYPES: readonly ["messages", "artifacts", "agreements", "tasks"];
1
+ export declare const CONTEXT_READ_TYPES: readonly ["artifacts", "agreements", "tasks"];
2
2
  export type ContextReadType = (typeof CONTEXT_READ_TYPES)[number];
3
3
  /**
4
- * Entry points a read can name as `via=<kind>:<id>` — the server's `parseVia`
5
- * grammar. `counterparty` parses but reads nothing: it resolves a scope graph,
6
- * not content, which is why {@link CONTEXT_READ_VIA} admits it for no type.
4
+ * Retained resource entry points for context reads and snapshots.
5
+ * The server still accepts legacy room entries until coordinated removal;
6
+ * new callers use only the domain resources below.
7
7
  */
8
- export declare const VIA_KINDS: readonly ["chat", "agreement", "task", "counterparty", "artifact"];
8
+ export declare const VIA_KINDS: readonly ["agreement", "task", "artifact", "message"];
9
9
  export type ViaKind = (typeof VIA_KINDS)[number];
10
10
  /**
11
- * Which entry points each read type actually accepts, mirroring the server's
11
+ * Which entry points each read type actually accepts, using the retained subset of the server's
12
12
  * `CONTEXT_READ_VIA`. Stated once here so the tool descriptions, the argument
13
13
  * check, and this type all read off the same list instead of three prose
14
14
  * copies that drift — and so a wrong pairing is refused with the reason rather
15
15
  * than as a bare 400 from a round-trip away.
16
16
  */
17
17
  export declare const CONTEXT_READ_VIA: Record<ContextReadType, readonly ViaKind[]>;
18
- /** `chat:<id>, agreement:<id>` — the accepted entries for one read type, for humans. */
18
+ /** `agreement:<id>, task:<id>` — the accepted entries for one read type, for humans. */
19
19
  export declare function viaHint(type: ContextReadType): string;
20
- /** Split `chat:abc` into its parts, or null when it is not a `via` at all. */
20
+ /** Split `agreement:abc` into its parts, or null when it is not a `via` at all. */
21
21
  export declare function parseVia(via: string): {
22
22
  kind: ViaKind;
23
23
  id: string;
@@ -43,7 +43,7 @@ export interface ContextReadEnvelope<T = unknown> {
43
43
  nextCursor: string | null;
44
44
  latestSequence?: string | null;
45
45
  }
46
- /** Aggregated chat snapshot from `GET /context/snapshot` (follow-up). */
46
+ /** Authorized resource snapshot from `GET /context/snapshot`. */
47
47
  /**
48
48
  * One participant on the snapshot roster. `kind` is the declared holder kind the
49
49
  * server stamped on the row, so a reader takes what a participant IS from the
@@ -56,6 +56,14 @@ export interface ContextSnapshotParticipant {
56
56
  isYou?: boolean;
57
57
  [key: string]: unknown;
58
58
  }
59
+ export interface ContextSnapshotQuery {
60
+ /** Exact message, task, agreement, or artifact entry; no implicit room. */
61
+ via: string;
62
+ agreementId?: string;
63
+ artifactId?: string;
64
+ maxMessages?: number;
65
+ contextGrantId?: string;
66
+ }
59
67
  export interface ContextSnapshotResult {
60
68
  /** Server-resolved orientation; writes recheck authority. */
61
69
  workBrief?: Record<string, unknown> & {
@@ -64,14 +72,14 @@ export interface ContextSnapshotResult {
64
72
  work?: Record<string, unknown>;
65
73
  history: unknown[];
66
74
  agreements: unknown[];
67
- /** Bounded room inventory, without bodies. Absent on older backends. */
75
+ /** Bounded authorized artifact inventory, without bodies. */
68
76
  artifacts?: ContextReadEnvelope<{
69
77
  artifactId: string;
70
78
  name: string | null;
71
79
  contentType: string;
72
80
  timestamp: string | null;
73
81
  }>;
74
- /** Direct artifact grants missing from the room inventory. Id and name only. */
82
+ /** Direct artifact grants outside the selected work inventory. Id and name only. */
75
83
  grantedArtifacts?: {
76
84
  items: Array<{
77
85
  artifactId: string;
@@ -87,11 +95,10 @@ export interface ContextSnapshotResult {
87
95
  userId: string;
88
96
  }>;
89
97
  latestSequence?: string | null;
90
- chatMissing?: boolean;
91
98
  }
92
99
  /**
93
100
  * Protocol-first uniform context reads.
94
- * Wraps `GET /context/read/:type` — one client, one envelope, four types.
101
+ * Wraps `GET /context/read/:type` — one client, one envelope, three types.
95
102
  */
96
103
  export declare class ContextReadClient {
97
104
  private readonly operatorKey;
@@ -100,18 +107,7 @@ export declare class ContextReadClient {
100
107
  /** Authority is supplied by withWorkExecution / withWorkExecutionId. */
101
108
  constructor(operatorKey: string, agentId?: string, baseUrl?: string);
102
109
  read<T = unknown>(type: ContextReadType, query: ContextReadQuery): Promise<ContextReadEnvelope<T>>;
103
- /**
104
- * Aggregated chat snapshot — `GET /context/snapshot?via=chat:<id>`. The
105
- * server composes history + agreements + roster through the same grant-fenced
106
- * readers as {@link read}, so hosted agents pull one fenced view per poll cycle.
107
- * Rejects with an `Error & { status }` on non-OK so callers can branch (404).
108
- */
109
- snapshot(chatId: string, opts?: {
110
- maxMessages?: number;
111
- contextGrantId?: string;
112
- agreementId?: string;
113
- taskId?: string;
114
- artifactId?: string;
115
- via?: string;
116
- }): Promise<ContextSnapshotResult>;
110
+ /** A snapshot selects one domain resource; the current execution headers
111
+ * remain authoritative and are never cleared after a refusal. */
112
+ snapshot(opts: ContextSnapshotQuery): Promise<ContextSnapshotResult>;
117
113
  }
@@ -2,41 +2,38 @@ import { getBackendUrl } from '../utils/urlUtils.js';
2
2
  import { pollSurfaceError } from '../shared/rateLimit.js';
3
3
  import { buildOperatorHeaders } from './operatorHeaders.js';
4
4
  export const CONTEXT_READ_TYPES = [
5
- 'messages',
6
5
  'artifacts',
7
6
  'agreements',
8
7
  'tasks',
9
8
  ];
10
9
  /**
11
- * Entry points a read can name as `via=<kind>:<id>` — the server's `parseVia`
12
- * grammar. `counterparty` parses but reads nothing: it resolves a scope graph,
13
- * not content, which is why {@link CONTEXT_READ_VIA} admits it for no type.
10
+ * Retained resource entry points for context reads and snapshots.
11
+ * The server still accepts legacy room entries until coordinated removal;
12
+ * new callers use only the domain resources below.
14
13
  */
15
14
  export const VIA_KINDS = [
16
- 'chat',
17
15
  'agreement',
18
16
  'task',
19
- 'counterparty',
20
17
  'artifact',
18
+ 'message',
21
19
  ];
22
20
  /**
23
- * Which entry points each read type actually accepts, mirroring the server's
21
+ * Which entry points each read type actually accepts, using the retained subset of the server's
24
22
  * `CONTEXT_READ_VIA`. Stated once here so the tool descriptions, the argument
25
23
  * check, and this type all read off the same list instead of three prose
26
24
  * copies that drift — and so a wrong pairing is refused with the reason rather
27
25
  * than as a bare 400 from a round-trip away.
28
26
  */
29
27
  export const CONTEXT_READ_VIA = {
30
- messages: ['chat'],
31
- artifacts: ['chat', 'agreement', 'task', 'artifact'],
32
- agreements: ['chat', 'agreement'],
28
+ artifacts: ['agreement', 'task', 'artifact'],
29
+ agreements: ['agreement'],
33
30
  tasks: ['agreement', 'task'],
34
31
  };
35
- /** `chat:<id>, agreement:<id>` — the accepted entries for one read type, for humans. */
32
+ /** `agreement:<id>, task:<id>` — the accepted entries for one read type, for humans. */
36
33
  export function viaHint(type) {
37
34
  return CONTEXT_READ_VIA[type].map((k) => `${k}:<id>`).join(', ');
38
35
  }
39
- /** Split `chat:abc` into its parts, or null when it is not a `via` at all. */
36
+ /** Split `agreement:abc` into its parts, or null when it is not a `via` at all. */
40
37
  export function parseVia(via) {
41
38
  const at = via.indexOf(':');
42
39
  if (at <= 0)
@@ -51,7 +48,7 @@ export function parseVia(via) {
51
48
  }
52
49
  /**
53
50
  * Protocol-first uniform context reads.
54
- * Wraps `GET /context/read/:type` — one client, one envelope, four types.
51
+ * Wraps `GET /context/read/:type` — one client, one envelope, three types.
55
52
  */
56
53
  export class ContextReadClient {
57
54
  operatorKey;
@@ -69,6 +66,10 @@ export class ContextReadClient {
69
66
  if (!query.via?.trim()) {
70
67
  throw new Error('ContextReadClient.read: via is required');
71
68
  }
69
+ const parsed = parseVia(query.via.trim());
70
+ if (!parsed || !CONTEXT_READ_VIA[type]?.includes(parsed.kind)) {
71
+ throw new Error(`ContextReadClient.read: ${type} reads accept via=${CONTEXT_READ_VIA[type] ? viaHint(type) : 'a retained resource'}`);
72
+ }
72
73
  const url = new URL(`${this.baseUrl}/context/read/${encodeURIComponent(type)}`);
73
74
  url.searchParams.set('via', query.via.trim());
74
75
  if (query.agreementId)
@@ -101,18 +102,18 @@ export class ContextReadClient {
101
102
  }
102
103
  return JSON.parse(body);
103
104
  }
104
- /**
105
- * Aggregated chat snapshot — `GET /context/snapshot?via=chat:<id>`. The
106
- * server composes history + agreements + roster through the same grant-fenced
107
- * readers as {@link read}, so hosted agents pull one fenced view per poll cycle.
108
- * Rejects with an `Error & { status }` on non-OK so callers can branch (404).
109
- */
110
- async snapshot(chatId, opts = {}) {
111
- if (!chatId?.trim()) {
112
- throw new Error('ContextReadClient.snapshot: chatId is required');
105
+ /** A snapshot selects one domain resource; the current execution headers
106
+ * remain authoritative and are never cleared after a refusal. */
107
+ async snapshot(opts) {
108
+ const via = opts?.via?.trim();
109
+ if (!via || !parseVia(via)) {
110
+ throw new Error('ContextReadClient.snapshot: an explicit resource is required (message:<id>, task:<id>, agreement:<id>, or artifact:<id>)');
111
+ }
112
+ if (parseVia(via)?.kind === 'message' && (opts.agreementId || opts.artifactId)) {
113
+ throw new Error('ContextReadClient.snapshot: select an exact message independently of other resources');
113
114
  }
114
115
  const url = new URL(`${this.baseUrl}/context/snapshot`);
115
- url.searchParams.set('via', opts.taskId ? `task:${opts.taskId}` : opts.via ?? (chatId.startsWith('agrn-') ? `agreement:${opts.agreementId ?? chatId.slice(5)}` : `chat:${chatId}`));
116
+ url.searchParams.set('via', via);
116
117
  if (opts.agreementId)
117
118
  url.searchParams.set('agreementId', opts.agreementId);
118
119
  if (opts.artifactId)
@@ -1,3 +1,7 @@
1
- import type { WorkExecutionContext } from '@ziggs-ai/contracts';
1
+ import type { ActionRef, WorkExecutionContext } from '@ziggs-ai/contracts';
2
2
  /** Fail closed before resuming model state, including timers and recorded wakes. */
3
3
  export declare function validateWorkExecution(context: WorkExecutionContext, operatorKey: string, baseUrl?: string): Promise<void>;
4
+ /** Explicit admission, outside an existing execution. Never retries a refused context. */
5
+ export declare function prepareWorkExecution(action: ActionRef, operatorKey: string, agentId: string, baseUrl?: string): Promise<WorkExecutionContext>;
6
+ /** The host holds a fenced lease while it runs; release is not domain completion. */
7
+ export declare function runWorkExecution<T>(context: WorkExecutionContext, operatorKey: string, run: (context: WorkExecutionContext) => Promise<T>, baseUrl?: string): Promise<T>;
@@ -1,5 +1,5 @@
1
1
  import { getBackendUrl } from '../utils/urlUtils.js';
2
- import { buildOperatorHeaders, withWorkExecution } from './operatorHeaders.js';
2
+ import { buildOperatorHeaders, withWorkExecution, withoutExecution } from './operatorHeaders.js';
3
3
  import { throwApiError } from '../shared/apiError.js';
4
4
  /** Fail closed before resuming model state, including timers and recorded wakes. */
5
5
  export async function validateWorkExecution(context, operatorKey, baseUrl = getBackendUrl()) {
@@ -15,3 +15,50 @@ export async function validateWorkExecution(context, operatorKey, baseUrl = getB
15
15
  throw new Error('Execution preflight returned a different context');
16
16
  });
17
17
  }
18
+ /** Explicit admission, outside an existing execution. Never retries a refused context. */
19
+ export async function prepareWorkExecution(action, operatorKey, agentId, baseUrl = getBackendUrl()) {
20
+ const response = await fetch(`${baseUrl}/execution/prepare`, {
21
+ method: 'POST', headers: { ...buildOperatorHeaders(operatorKey, agentId), 'content-type': 'application/json' },
22
+ body: JSON.stringify(action),
23
+ });
24
+ const text = await response.text();
25
+ if (!response.ok)
26
+ throwApiError(response, text, 'Action execution is unavailable');
27
+ const context = JSON.parse(text);
28
+ if (!context.contextId || context.actorId !== agentId)
29
+ throw new Error('Invalid action execution response');
30
+ return context;
31
+ }
32
+ /** The host holds a fenced lease while it runs; release is not domain completion. */
33
+ export async function runWorkExecution(context, operatorKey, run, baseUrl = getBackendUrl()) {
34
+ const call = async (body, selected = context) => withWorkExecution(selected, async () => {
35
+ const finish = body.operation === 'finish';
36
+ const headers = buildOperatorHeaders(operatorKey, context.actorId);
37
+ const response = await fetch(`${baseUrl}/execution/attempt`, { method: 'POST',
38
+ headers: { ...(finish ? withoutExecution(headers) : headers), 'content-type': 'application/json' },
39
+ body: JSON.stringify(finish ? { ...body, contextId: selected.contextId, attemptId: selected.attemptId } : body) });
40
+ const text = await response.text();
41
+ if (!response.ok)
42
+ throwApiError(response, text, 'Execution attempt refused');
43
+ return JSON.parse(text);
44
+ });
45
+ const attempt = await call({ operation: 'claim' });
46
+ if (attempt.contextId !== context.contextId || !attempt.attemptId)
47
+ throw new Error('Invalid execution attempt response');
48
+ let lost;
49
+ const timer = setInterval(() => { void call({ operation: 'renew' }, attempt).catch(error => { lost = error; }); }, 20_000);
50
+ timer.unref();
51
+ try {
52
+ const result = await withWorkExecution(attempt, () => run(attempt));
53
+ if (lost)
54
+ throw lost;
55
+ return result;
56
+ }
57
+ finally {
58
+ clearInterval(timer);
59
+ // A stale worker never releases its replacement. Failure here keeps durable
60
+ // inputs pending; it must not transform an execution refusal into a retry
61
+ // without its selected authority.
62
+ await call({ operation: 'finish' }, attempt);
63
+ }
64
+ }
@@ -6,8 +6,10 @@ export type IntroductionStatus = 'open' | 'redeemed' | 'declined' | 'revoked' |
6
6
  * (your own token, a teammate's, or someone you are already linked to).
7
7
  * `claimed` / `bid` / `accepted`: the listing, request or proposal it names.
8
8
  * `link_pending`: a link both people still approve.
9
+ * `held`: an agent with no person around holds it as a guest until a person
10
+ * claims that agent; the claim finishes the redeem as them.
9
11
  */
10
- export type IntroductionOutcome = 'joined' | 'claimed' | 'bid' | 'accepted' | 'link_pending';
12
+ export type IntroductionOutcome = 'joined' | 'claimed' | 'bid' | 'accepted' | 'link_pending' | 'held';
11
13
  export interface IntroductionFrom {
12
14
  orgId: string;
13
15
  agentId: string | null;
@@ -34,7 +36,7 @@ export interface IntroductionView {
34
36
  note: string | null;
35
37
  /** What the token is about. The public view gives kind and title only. */
36
38
  about?: {
37
- kind: 'agreement' | 'task' | 'chat' | 'workspace';
39
+ kind: 'agreement' | 'task' | 'address' | 'workspace';
38
40
  id?: string;
39
41
  title: string | null;
40
42
  shape: 'listing' | 'request' | 'proposal' | null;
@@ -44,13 +46,23 @@ export interface IntroductionView {
44
46
  allowed: number;
45
47
  left: number;
46
48
  };
49
+ /** Ziggs emailed it, and only the person signed in with that address redeems it. */
50
+ addressed?: boolean;
47
51
  agreementId: string | null;
48
52
  redeemedBy: {
49
53
  agentId: string | null;
50
54
  orgId: string | null;
51
55
  } | null;
56
+ /** The minter's list only: each redeemer and where they ended. */
57
+ redemptions?: Array<{
58
+ agentId: string | null;
59
+ orgId: string | null;
60
+ outcome: IntroductionOutcome;
61
+ agreementId: string | null;
62
+ at: string;
63
+ }>;
52
64
  /** Returned to a redeemer only: the room its agent is now in. */
53
- chatId?: string | null;
65
+ addressId?: string | null;
54
66
  expiresAt: string;
55
67
  /** The public page the string points at. This is what you hand over. */
56
68
  url: string;
@@ -77,6 +89,17 @@ export interface MintIntroductionInput {
77
89
  venue?: string;
78
90
  venueRef?: string;
79
91
  note?: string;
92
+ /** What the token is about: a listing, request or proposal (agreement), a task, or a chat you hold. */
93
+ about?: {
94
+ kind: 'agreement' | 'task' | 'address' | 'workspace';
95
+ id: string;
96
+ };
97
+ /** How many people it serves, 1 to 25. Your own sessions never use one up. */
98
+ redeems?: number;
99
+ /** A workspace string: the email it is for. Only that person redeems it. */
100
+ email?: string;
101
+ /** A workspace string: the role it gives (default member). */
102
+ role?: 'owner' | 'admin' | 'member';
80
103
  /** What this text drew on. See {@link WriteSource}. */
81
104
  sources?: WriteSource[];
82
105
  }
@@ -93,7 +116,13 @@ export declare class IntroductionsClient {
93
116
  private readonly baseUrl;
94
117
  constructor(operatorKey: string, agentId?: string, baseUrl?: string);
95
118
  mint(input?: MintIntroductionInput): Promise<IntroductionView>;
96
- redeem(token: string): Promise<IntroductionView>;
119
+ /**
120
+ * A workspace string joins only with `confirm: true`, which is the person's
121
+ * yes: without it the answer names the workspace and asks for that yes.
122
+ */
123
+ redeem(token: string, opts?: {
124
+ confirm?: boolean;
125
+ }): Promise<IntroductionView>;
97
126
  decline(token: string): Promise<IntroductionView>;
98
127
  revoke(token: string): Promise<IntroductionView>;
99
128
  listMine(limit?: number): Promise<IntroductionView[]>;
@@ -23,8 +23,12 @@ export class IntroductionsClient {
23
23
  async mint(input = {}) {
24
24
  return this.call('POST', '/introductions', input, 'mint');
25
25
  }
26
- async redeem(token) {
27
- return this.call('POST', `/introductions/${encodeURIComponent(token)}/redeem`, {}, 'redeem');
26
+ /**
27
+ * A workspace string joins only with `confirm: true`, which is the person's
28
+ * yes: without it the answer names the workspace and asks for that yes.
29
+ */
30
+ async redeem(token, opts = {}) {
31
+ return this.call('POST', `/introductions/${encodeURIComponent(token)}/redeem`, opts.confirm ? { confirm: true } : {}, 'redeem');
28
32
  }
29
33
  async decline(token) {
30
34
  return this.call('POST', `/introductions/${encodeURIComponent(token)}/decline`, {}, 'decline');
@@ -0,0 +1,36 @@
1
+ import type { MessageCommand, MessageCommandResult, MessageRecord, MessagingControls } from '@ziggs-ai/contracts';
2
+ export interface MessageQuery {
3
+ q?: string;
4
+ senderId?: string;
5
+ recipientIds?: string[];
6
+ agreementId?: string;
7
+ taskId?: string;
8
+ participantIds?: string[];
9
+ before?: string;
10
+ limit?: number;
11
+ }
12
+ export interface MessagePage {
13
+ messages: MessageRecord[];
14
+ nextCursor: string | null;
15
+ }
16
+ /** One command and one representation, independent of navigation. */
17
+ export declare class MessageRecordsClient {
18
+ private readonly operatorKey;
19
+ private readonly agentId;
20
+ private readonly baseUrl?;
21
+ constructor(operatorKey: string, agentId: string, baseUrl?: string | undefined);
22
+ private request;
23
+ get(id: string): Promise<MessageRecord>;
24
+ send(command: MessageCommand): Promise<MessageCommandResult>;
25
+ list(input?: MessageQuery): Promise<MessagePage>;
26
+ replies(id: string, input?: {
27
+ before?: string;
28
+ limit?: number;
29
+ }): Promise<MessagePage>;
30
+ controls(agreementId: string, taskId?: string): Promise<MessagingControls>;
31
+ answers(id: string): Promise<unknown>;
32
+ cancelAnswer(id: string, requestId: string): Promise<{
33
+ state: 'cancelled';
34
+ }>;
35
+ }
36
+ export type { MessageCommand, MessageCommandResult, MessageRecord } from '@ziggs-ai/contracts';