@ziggs-ai/api-client 0.20.0 → 0.22.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.
- package/dist/capabilities/agreementVerbs.d.ts +7 -0
- package/dist/capabilities/agreementVerbs.js +84 -1
- package/dist/capabilities/artifacts.js +53 -26
- package/dist/capabilities/chat.d.ts +31 -1
- package/dist/capabilities/chat.js +40 -1
- package/dist/capabilities/context.d.ts +1 -0
- package/dist/capabilities/context.js +46 -5
- package/dist/capabilities/discovery.js +10 -4
- package/dist/capabilities/index.d.ts +4 -4
- package/dist/capabilities/index.js +4 -4
- package/dist/capabilities/marketplace.js +15 -6
- package/dist/capabilities/nextCall.js +62 -4
- package/dist/capabilities/tasks.d.ts +68 -1
- package/dist/capabilities/tasks.js +112 -3
- package/dist/decisionWords.d.ts +51 -0
- package/dist/decisionWords.js +86 -0
- package/dist/engagementGuide.d.ts +148 -0
- package/dist/engagementGuide.js +349 -0
- package/dist/http/AgreementClient.d.ts +2 -1
- package/dist/http/AgreementClient.js +4 -0
- package/dist/http/ChatClient.d.ts +28 -2
- package/dist/http/ChatClient.js +7 -0
- package/dist/http/ContextGrantsClient.d.ts +17 -1
- package/dist/http/ContextGrantsClient.js +26 -2
- package/dist/http/ContextReadClient.d.ts +8 -1
- package/dist/http/TaskClient.d.ts +14 -2
- package/dist/http/TaskClient.js +21 -6
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/types.d.ts +11 -0
- package/package.json +3 -3
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to do with a piece of engagement, from records the caller already has.
|
|
3
|
+
*
|
|
4
|
+
* This is a receipt and a next-call pointer. It does not invent a worker
|
|
5
|
+
* protocol, a wake store, or a blocked task state. Waiting-on-person is a
|
|
6
|
+
* field on the receipt so a later human view can show waiting instead of
|
|
7
|
+
* active. A held graph is not work until deps release; failed or cancelled
|
|
8
|
+
* deps are not inputs. Withdraw is cancel on the root. A live hire does not
|
|
9
|
+
* take a second hire or an in-place amend.
|
|
10
|
+
*/
|
|
11
|
+
import { agreementBidCapability, agreementBuyCapability, agreementRequestCapability, agreementSubcontractCapability, } from './capabilities/agreementVerbs.js';
|
|
12
|
+
import { nextCall } from './capabilities/nextCall.js';
|
|
13
|
+
const TERMINAL_TASK = new Set(['completed', 'failed', 'cancelled']);
|
|
14
|
+
function openTasksForAgreement(tasks, agreementId) {
|
|
15
|
+
return (tasks ?? []).filter((t) => t.agreementId === agreementId && !TERMINAL_TASK.has(t.state));
|
|
16
|
+
}
|
|
17
|
+
function assignedToMe(delivery, agentId) {
|
|
18
|
+
return Boolean(agentId) && delivery.assigneeId === agentId;
|
|
19
|
+
}
|
|
20
|
+
/** Same event twice, or a hire-room brief that already has an open task. */
|
|
21
|
+
export function isEchoOrDuplicate(inbox, agentId) {
|
|
22
|
+
const mine = inbox.deliveries.filter((d) => assignedToMe(d, agentId));
|
|
23
|
+
const eventIds = mine.map((d) => d.eventId).filter(Boolean);
|
|
24
|
+
if (new Set(eventIds).size < eventIds.length)
|
|
25
|
+
return true;
|
|
26
|
+
for (const d of mine) {
|
|
27
|
+
if (d.kind === 'task-state' && d.taskId) {
|
|
28
|
+
const already = (inbox.tasksAwaitingMe ?? []).some((t) => t.taskId === d.taskId);
|
|
29
|
+
if (already)
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (d.kind === 'message' && d.agreementId) {
|
|
33
|
+
if (openTasksForAgreement(inbox.tasksAwaitingMe, d.agreementId).length > 0) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Assigned message under a live hire. Missing chat is not a skip — a fresh
|
|
42
|
+
* hire without a room still runs after access to that agreement is proven.
|
|
43
|
+
* An agrn- notification lane is not treated as a chat row.
|
|
44
|
+
*/
|
|
45
|
+
export function isHireWorkOrder(delivery, agentId, tasksAwaitingMe) {
|
|
46
|
+
if (!assignedToMe(delivery, agentId))
|
|
47
|
+
return false;
|
|
48
|
+
if (delivery.kind !== 'message')
|
|
49
|
+
return false;
|
|
50
|
+
if (!delivery.agreementId)
|
|
51
|
+
return false;
|
|
52
|
+
if (delivery.taskId)
|
|
53
|
+
return false;
|
|
54
|
+
if (openTasksForAgreement(tasksAwaitingMe, delivery.agreementId).length > 0) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
export function waitingOnFromTask(task) {
|
|
60
|
+
if (task.waitingOn?.id)
|
|
61
|
+
return task.waitingOn;
|
|
62
|
+
if (task.lastAct === 'question' && task.waitingOn?.id)
|
|
63
|
+
return task.waitingOn;
|
|
64
|
+
return task.waitingOn ?? null;
|
|
65
|
+
}
|
|
66
|
+
export function graphReleaseHonesty(task) {
|
|
67
|
+
const failedDepCount = (task.waitsOn ?? []).filter((id) => {
|
|
68
|
+
const state = task.depStates?.[id];
|
|
69
|
+
return state === 'failed' || state === 'cancelled';
|
|
70
|
+
}).length;
|
|
71
|
+
return {
|
|
72
|
+
ready: !task.heldByDeps,
|
|
73
|
+
heldByDeps: Boolean(task.heldByDeps),
|
|
74
|
+
failedDepsAreNotInputs: true,
|
|
75
|
+
failedDepCount,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function liveHireChangedTerms(env) {
|
|
79
|
+
return {
|
|
80
|
+
kind: 'hire_terms',
|
|
81
|
+
outcome: 'unavailable',
|
|
82
|
+
summary: 'A live hire does not take a second hire or an in-place amend. Reuse the active agreement; changing terms is not on this surface.',
|
|
83
|
+
hireTerms: { secondHire: 'refused', amendLive: 'unavailable' },
|
|
84
|
+
next: nextCall(env, 'agreement_claim', undefined, 'a second hire on live terms is refused — reuse the active agreement', { outcome: 'unavailable' }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Map a hire-formation 409 onto the refuse-second-hire receipt. */
|
|
88
|
+
export function liveHireConflictFromStatus(env, status) {
|
|
89
|
+
if (status === 409)
|
|
90
|
+
return liveHireChangedTerms(env);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
export function withdrawHeldGraph(env, rootTaskId) {
|
|
94
|
+
return {
|
|
95
|
+
kind: 'held',
|
|
96
|
+
outcome: 'pending',
|
|
97
|
+
taskId: rootTaskId,
|
|
98
|
+
summary: 'This graph is held. Withdraw is one act: cancel the root. Do not invent a blocked state.',
|
|
99
|
+
graph: {
|
|
100
|
+
ready: false,
|
|
101
|
+
heldByDeps: true,
|
|
102
|
+
failedDepsAreNotInputs: true,
|
|
103
|
+
failedDepCount: 0,
|
|
104
|
+
},
|
|
105
|
+
next: nextCall(env, 'task_cancel', { taskId: rootTaskId }, 'withdraw the held graph by cancelling the root'),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function reminderHonesty(task, continuation) {
|
|
109
|
+
const limits = task.checkBack
|
|
110
|
+
? `Remind at most ${task.checkBack.maxReminders} times, no sooner than every ${task.checkBack.everyMinutes} minutes.`
|
|
111
|
+
: 'This task has no reminder budget.';
|
|
112
|
+
const deadline = task.dueAt
|
|
113
|
+
? ` At ${task.dueAt} post what you have and stop.`
|
|
114
|
+
: '';
|
|
115
|
+
if (continuation && !continuation.canScheduleWake) {
|
|
116
|
+
return {
|
|
117
|
+
kind: 'reminder',
|
|
118
|
+
outcome: 'unavailable',
|
|
119
|
+
taskId: task.taskId,
|
|
120
|
+
summary: `${limits}${deadline} This session cannot arm a wake — continue only by reading the inbox again. Do not infer a scheduler from the transport.`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
kind: 'reminder',
|
|
125
|
+
outcome: 'ok',
|
|
126
|
+
taskId: task.taskId,
|
|
127
|
+
summary: `${limits}${deadline} Wake capability is whatever this host already reported — not the profile or the transport.`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Formation receipt. Reuse a matching active agreement. Claim a listing
|
|
132
|
+
* and never counter it. Request when nothing listed fits. Direct only when
|
|
133
|
+
* they accept proposals. Subcontract under a parent you already hold.
|
|
134
|
+
* A second formation on a live hire is refused.
|
|
135
|
+
*/
|
|
136
|
+
export function formationGuide(env, input) {
|
|
137
|
+
const matchingId = input.matchingActive?.agreementId;
|
|
138
|
+
if (matchingId) {
|
|
139
|
+
return {
|
|
140
|
+
kind: 'reuse',
|
|
141
|
+
outcome: 'noop',
|
|
142
|
+
agreementId: matchingId,
|
|
143
|
+
summary: 'An active agreement already covers this work on matching terms. Reuse it. Do not form another, and do not counter a listing to replace it.',
|
|
144
|
+
next: nextCall(env, 'agreement_get', { agreementId: matchingId }, 'open the agreement you already hold'),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (input.liveHire?.agreementId && input.wantsChangedTerms) {
|
|
148
|
+
return {
|
|
149
|
+
...liveHireChangedTerms(env),
|
|
150
|
+
agreementId: input.liveHire.agreementId,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (input.liveHire?.agreementId) {
|
|
154
|
+
return {
|
|
155
|
+
kind: 'reuse',
|
|
156
|
+
outcome: 'noop',
|
|
157
|
+
agreementId: input.liveHire.agreementId,
|
|
158
|
+
summary: 'You already hold a live hire with this party. Reuse it. Do not open a second formation.',
|
|
159
|
+
next: nextCall(env, 'agreement_get', { agreementId: input.liveHire.agreementId }, 'open the live hire you already hold'),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (input.parentAgreementId) {
|
|
163
|
+
return {
|
|
164
|
+
kind: 'subcontract',
|
|
165
|
+
outcome: 'ok',
|
|
166
|
+
agreementId: input.parentAgreementId,
|
|
167
|
+
summary: 'Subcontracting under an active parent is its own rail. Name the worker and the slice. Do not open a second top-level hire.',
|
|
168
|
+
next: nextCall(env, 'agreement_subcontract', { parentAgreementId: input.parentAgreementId }, 'delegate this slice under the parent you hold', { definition: agreementSubcontractCapability }),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (input.listing) {
|
|
172
|
+
const agreementId = input.listing === true ? undefined : input.listing.agreementId;
|
|
173
|
+
return {
|
|
174
|
+
kind: 'claim',
|
|
175
|
+
outcome: 'ok',
|
|
176
|
+
...(agreementId ? { agreementId } : {}),
|
|
177
|
+
summary: 'Claim the posted listing. Listings are take-it-or-leave-it — never counter one. Do not buy past a listing that already fits.',
|
|
178
|
+
next: nextCall(env, 'agreement_claim', agreementId ? { agreementId } : undefined, 'claim this listing by agreementId — never counter it'),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (input.counterparty && input.acceptsProposals === false) {
|
|
182
|
+
return {
|
|
183
|
+
kind: 'claim',
|
|
184
|
+
outcome: 'unavailable',
|
|
185
|
+
summary: 'This agent is claim-only and refuses a direct proposal. Find their listing and claim it. Do not buy, bid, or counter.',
|
|
186
|
+
next: nextCall(env, 'marketplace_view', undefined, 'find the listing this agent already posted'),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (input.counterparty && input.acceptsProposals) {
|
|
190
|
+
const key = input.direction === 'bid' ? 'agreement_bid' : 'agreement_buy';
|
|
191
|
+
return {
|
|
192
|
+
kind: 'direct',
|
|
193
|
+
outcome: 'ok',
|
|
194
|
+
summary: 'No listing and they accept proposals. Go direct for these terms. Most published agents are claim-only — this path is the exception.',
|
|
195
|
+
next: nextCall(env, key, { counterparty: input.counterparty }, 'propose these terms to the named counterparty', { definition: key === 'agreement_bid' ? agreementBidCapability : agreementBuyCapability }),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
kind: 'request',
|
|
200
|
+
outcome: 'ok',
|
|
201
|
+
summary: 'Nothing listed fits. Post a request and let a claimer take it. Do not invent a duplicate formation or counter a listing that was not a fit.',
|
|
202
|
+
next: nextCall(env, 'agreement_request', undefined, 'post a request when nothing listed fits', { definition: agreementRequestCapability }),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/** Browse result: claim when rows exist, request when the board is empty. */
|
|
206
|
+
export function marketplaceFormation(env, input) {
|
|
207
|
+
const ids = input.listingAgreementIds.filter(Boolean);
|
|
208
|
+
if (ids.length === 1) {
|
|
209
|
+
return formationGuide(env, { listing: { agreementId: ids[0] } });
|
|
210
|
+
}
|
|
211
|
+
if (ids.length > 1) {
|
|
212
|
+
return formationGuide(env, { listing: true });
|
|
213
|
+
}
|
|
214
|
+
return formationGuide(env, {});
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Contact path. Same-org people are reachable under the scoped identity.
|
|
218
|
+
* An outside person needs an active link. A link is not a roster. An agent
|
|
219
|
+
* with a listing is claimed, not chatted into a hire.
|
|
220
|
+
*/
|
|
221
|
+
export function contactPath(env, input) {
|
|
222
|
+
const { partyKind, partyId, linked, listed, sameOrg } = input;
|
|
223
|
+
if (partyKind === 'person' && (linked || sameOrg)) {
|
|
224
|
+
return {
|
|
225
|
+
kind: 'contact',
|
|
226
|
+
outcome: 'ok',
|
|
227
|
+
summary: sameOrg && !linked
|
|
228
|
+
? 'Same-org person: reach them under this scoped identity.'
|
|
229
|
+
: 'Linked person: open or continue the working room. A link does not reveal their org roster.',
|
|
230
|
+
next: nextCall(env, 'chat_open', { participantId: partyId }, 'reach this person in a working room'),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (partyKind === 'person' && !linked) {
|
|
234
|
+
return {
|
|
235
|
+
kind: 'contact',
|
|
236
|
+
outcome: 'pending',
|
|
237
|
+
summary: 'Unlinked outside person: request a link or introduction. Do not invent an agreement or listing to manufacture reach.',
|
|
238
|
+
next: nextCall(env, 'link_propose', { to: partyId }, 'ask for a reach-only link to this person'),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (partyKind === 'agent' && listed) {
|
|
242
|
+
return {
|
|
243
|
+
kind: 'contact',
|
|
244
|
+
outcome: 'ok',
|
|
245
|
+
summary: 'This agent is listed. Claim the listing — do not open a hire by chatting.',
|
|
246
|
+
next: nextCall(env, 'marketplace_view', undefined, 'find the listing to claim'),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
kind: 'contact',
|
|
251
|
+
outcome: 'pending',
|
|
252
|
+
summary: 'This agent is not listed for a claim. Follow the engagement ladder (reuse, request, or a permitted direct proposal). Do not treat chat as a hire.',
|
|
253
|
+
next: nextCall(env, 'agreement_request', undefined, 'post a request when nothing listed fits', { definition: agreementRequestCapability }),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
export function hireWorkOrderGuide(env, delivery) {
|
|
257
|
+
const agreementId = delivery.agreementId;
|
|
258
|
+
return {
|
|
259
|
+
kind: 'work_order',
|
|
260
|
+
outcome: 'ok',
|
|
261
|
+
chatId: delivery.chatId,
|
|
262
|
+
agreementId,
|
|
263
|
+
summary: 'A brief from the principal in this hire is a work order. Create a task on yourself under that agreement. Fill the description from the brief after you read it. When you need helpers, declare a graph — do not invent worker_next.',
|
|
264
|
+
next: nextCall(env, 'task_create', {
|
|
265
|
+
agreementId,
|
|
266
|
+
assigneeId: env.creds.agentId,
|
|
267
|
+
}, 'create the work order on yourself under this hire'),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
export function strangerBriefGuide(env, chatId) {
|
|
271
|
+
return {
|
|
272
|
+
kind: 'stranger_brief',
|
|
273
|
+
outcome: 'pending',
|
|
274
|
+
chatId,
|
|
275
|
+
summary: "A stranger's brief is not a work order. Draft an agreement; do not create a task under a hire you do not have.",
|
|
276
|
+
next: nextCall(env, 'agreement_request', undefined, 'draft the agreement this brief would ride', { definition: agreementRequestCapability }),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
export function duplicateGuide() {
|
|
280
|
+
return {
|
|
281
|
+
kind: 'duplicate',
|
|
282
|
+
outcome: 'noop',
|
|
283
|
+
summary: 'This is a duplicate or echo of work you already have. Do not create another request, task, or committed effect.',
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function hintFromTask(task) {
|
|
287
|
+
return {
|
|
288
|
+
taskId: task.taskId,
|
|
289
|
+
agreementId: task.agreementId ?? null,
|
|
290
|
+
rootTaskId: task.rootTaskId ?? null,
|
|
291
|
+
heldByDeps: task.heldByDeps,
|
|
292
|
+
waitsOn: task.waitsOn,
|
|
293
|
+
dueAt: task.dueAt ?? null,
|
|
294
|
+
checkBack: task.checkBack ?? null,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* First engagement that matters on this envelope. Empty mail with no held
|
|
299
|
+
* tasks returns null so an empty inbox result stays a pass-through.
|
|
300
|
+
*/
|
|
301
|
+
export function inboxEngagement(input) {
|
|
302
|
+
const { env, agentId, inbox, continuation } = input;
|
|
303
|
+
const tasks = input.tasks ?? [];
|
|
304
|
+
if (isEchoOrDuplicate(inbox, agentId)) {
|
|
305
|
+
return duplicateGuide();
|
|
306
|
+
}
|
|
307
|
+
const mine = inbox.deliveries.filter((d) => assignedToMe(d, agentId));
|
|
308
|
+
const workOrder = mine.find((d) => isHireWorkOrder(d, agentId, inbox.tasksAwaitingMe));
|
|
309
|
+
if (workOrder) {
|
|
310
|
+
return hireWorkOrderGuide(env, workOrder);
|
|
311
|
+
}
|
|
312
|
+
const stranger = mine.find((d) => d.kind === 'message' && !d.agreementId);
|
|
313
|
+
if (stranger) {
|
|
314
|
+
return strangerBriefGuide(env, stranger.chatId);
|
|
315
|
+
}
|
|
316
|
+
const held = tasks.find((t) => t.heldByDeps);
|
|
317
|
+
if (held) {
|
|
318
|
+
const graph = graphReleaseHonesty(held);
|
|
319
|
+
const rootId = held.rootTaskId || held.taskId;
|
|
320
|
+
return {
|
|
321
|
+
kind: 'held',
|
|
322
|
+
outcome: 'pending',
|
|
323
|
+
taskId: held.taskId,
|
|
324
|
+
agreementId: held.agreementId ?? null,
|
|
325
|
+
summary: 'This node is held until dependencies are terminal. Failed or cancelled dependencies are not inputs. Withdraw by cancelling the root.',
|
|
326
|
+
graph,
|
|
327
|
+
next: nextCall(env, 'task_cancel', { taskId: rootId }, 'withdraw the held graph by cancelling the root'),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const waiting = tasks.find((t) => waitingOnFromTask(t));
|
|
331
|
+
if (waiting) {
|
|
332
|
+
const who = waitingOnFromTask(waiting);
|
|
333
|
+
return {
|
|
334
|
+
kind: 'waiting',
|
|
335
|
+
outcome: 'pending',
|
|
336
|
+
taskId: waiting.taskId,
|
|
337
|
+
waitingOn: who,
|
|
338
|
+
summary: 'The last act was a question. This task is waiting on that person — a receipt, not a new blocked state.',
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const chase = tasks.find((t) => t.dueAt || t.checkBack);
|
|
342
|
+
if (chase) {
|
|
343
|
+
return reminderHonesty(chase, continuation);
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
export function hintsFromTasks(tasks) {
|
|
348
|
+
return (tasks ?? []).map(hintFromTask);
|
|
349
|
+
}
|
|
@@ -120,7 +120,8 @@ export declare function proposeBroadcast(input: ProposeBroadcastInput, creds: Cr
|
|
|
120
120
|
export interface DelegateAgreementData {
|
|
121
121
|
description: string;
|
|
122
122
|
executorId: string;
|
|
123
|
-
|
|
123
|
+
/** Omit it and the server uses the parent agreement's own chat. */
|
|
124
|
+
chatId?: string;
|
|
124
125
|
parentAgreementId: string;
|
|
125
126
|
price?: number;
|
|
126
127
|
lifecycle?: string;
|
|
@@ -106,6 +106,10 @@ export function shapeAgreement(a) {
|
|
|
106
106
|
...(wire.terms?.maxExecutions != null
|
|
107
107
|
? { maxExecutions: wire.terms.maxExecutions }
|
|
108
108
|
: {}),
|
|
109
|
+
// A price with no billing mode is two different offers. Lifted
|
|
110
|
+
// beside the executions cap because they are read together: "ϟ5 a task, up
|
|
111
|
+
// to 10" and "ϟ5 for the lot" are the same three fields otherwise.
|
|
112
|
+
...(wire.terms?.billing != null ? { billing: wire.terms.billing } : {}),
|
|
109
113
|
...(wire.proposal?.status != null
|
|
110
114
|
? { proposalStatus: wire.proposal.status }
|
|
111
115
|
: {}),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChatReadDto } from '@ziggs-ai/contracts';
|
|
2
|
-
import type { SendChatMessageResult } from '@ziggs-ai/contracts';
|
|
2
|
+
import type { MessageDelivery, SendChatMessageResult as WireSendChatMessageResult } from '@ziggs-ai/contracts';
|
|
3
3
|
import { type Creds } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* One room from `GET /chats/mine`, as the server sends it.
|
|
@@ -45,7 +45,33 @@ export interface SendChatMessageInput {
|
|
|
45
45
|
contentType?: string;
|
|
46
46
|
underAgreementId?: string;
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Where the server says a send landed.
|
|
50
|
+
*
|
|
51
|
+
* The contract's own shape, re-exported rather than restated: it shipped in
|
|
52
|
+
* contracts 0.7.7, and the hand-written stand-in that carried it until then is
|
|
53
|
+
* gone. What the fields mean is worth keeping in front of a caller, because
|
|
54
|
+
* the receipt is easy to over-read:
|
|
55
|
+
*
|
|
56
|
+
* The caller may omit the receiver and have it inferred, or have it widened to
|
|
57
|
+
* the room's humans, so the destination is the server's answer rather than the
|
|
58
|
+
* caller's request. `accepted` is the message being created — not a claim that
|
|
59
|
+
* anyone read it. Routing is asynchronous and unconfirmed by this receipt; the
|
|
60
|
+
* `wakesAgent` field that once rode along was removed for exactly that reason
|
|
61
|
+
* and is not coming back.
|
|
62
|
+
*/
|
|
63
|
+
export type { MessageDelivery } from '@ziggs-ai/contracts';
|
|
64
|
+
/**
|
|
65
|
+
* The send result as a caller here holds it.
|
|
66
|
+
*
|
|
67
|
+
* `delivery` is optional on the way in and required in the contract, which is
|
|
68
|
+
* not a disagreement about the wire: this client is installed independently of
|
|
69
|
+
* the backend it talks to, and a server from before the field existed answers
|
|
70
|
+
* without one.
|
|
71
|
+
*/
|
|
72
|
+
export type SendChatMessageResult = Omit<WireSendChatMessageResult, 'delivery'> & {
|
|
73
|
+
delivery?: MessageDelivery;
|
|
74
|
+
};
|
|
49
75
|
export type ContextTemporal = 'from-now' | 'from-start';
|
|
50
76
|
export interface AddChatMemberInput {
|
|
51
77
|
chatId: string;
|
package/dist/http/ChatClient.js
CHANGED
|
@@ -112,11 +112,18 @@ export async function sendChatMessage(input, creds) {
|
|
|
112
112
|
throwApiError(res, body, `sendChatMessage failed: ${res.status} ${res.statusText}`);
|
|
113
113
|
}
|
|
114
114
|
const data = (await res.json().catch(() => null));
|
|
115
|
+
const delivery = data?.['delivery'];
|
|
115
116
|
return {
|
|
116
117
|
success: Boolean(data?.['success']),
|
|
117
118
|
message: String(data?.['message'] ?? 'ok'),
|
|
118
119
|
messageId: String(data?.['messageId'] ?? input.messageId),
|
|
119
120
|
chatId: String(data?.['chatId'] ?? chatId),
|
|
121
|
+
// Passed through as the server reported it, or absent. Never reconstructed
|
|
122
|
+
// from the request: the whole point is that the destination may not be the
|
|
123
|
+
// one the caller named.
|
|
124
|
+
...(delivery && typeof delivery === 'object'
|
|
125
|
+
? { delivery: delivery }
|
|
126
|
+
: {}),
|
|
120
127
|
};
|
|
121
128
|
}
|
|
122
129
|
export async function listMyChats(creds) {
|
|
@@ -96,7 +96,21 @@ export declare class ContextGrantsClient {
|
|
|
96
96
|
*/
|
|
97
97
|
constructor(operatorKey: string, agentId?: string, baseUrl?: string);
|
|
98
98
|
issueGrant(input: IssueContextGrantInput): Promise<GrantView>;
|
|
99
|
-
|
|
99
|
+
requestArtifactAccess(artifactId: string, input: {
|
|
100
|
+
chatId: string;
|
|
101
|
+
ownerId: string;
|
|
102
|
+
reason: string;
|
|
103
|
+
durationHours?: number;
|
|
104
|
+
}, opts?: {
|
|
105
|
+
idempotencyKey?: string;
|
|
106
|
+
laneId?: string;
|
|
107
|
+
}): Promise<{
|
|
108
|
+
status: 'pending_approval';
|
|
109
|
+
agreementId: string;
|
|
110
|
+
}>;
|
|
111
|
+
delegateGrant(parentGrantId: string, input: DelegateContextGrantInput, opts?: {
|
|
112
|
+
idempotencyKey?: string;
|
|
113
|
+
}): Promise<DelegateContextGrantResult>;
|
|
100
114
|
/**
|
|
101
115
|
* expand a grant you hold into the chat/agreement ids inside its
|
|
102
116
|
* scope. A grant is a fence, not a listing: discovery says "you hold
|
|
@@ -118,6 +132,8 @@ export declare class ContextGrantsClient {
|
|
|
118
132
|
holderId: string;
|
|
119
133
|
expiresAt?: string | null;
|
|
120
134
|
chatId?: string;
|
|
135
|
+
}, opts?: {
|
|
136
|
+
idempotencyKey?: string;
|
|
121
137
|
}): Promise<DelegateContextGrantResult>;
|
|
122
138
|
revokeGrant(grantId: string): Promise<{
|
|
123
139
|
status: string;
|
|
@@ -63,11 +63,32 @@ export class ContextGrantsClient {
|
|
|
63
63
|
}
|
|
64
64
|
return parsed.grant;
|
|
65
65
|
}
|
|
66
|
-
async
|
|
66
|
+
async requestArtifactAccess(artifactId, input, opts) {
|
|
67
|
+
const res = await fetch(`${this.baseUrl}/context/artifacts/${encodeURIComponent(artifactId)}/access-requests`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: buildOperatorHeaders(this.operatorKey, this.agentId, {
|
|
70
|
+
'content-type': 'application/json',
|
|
71
|
+
...(opts?.idempotencyKey ? { 'Idempotency-Key': opts.idempotencyKey } : {}),
|
|
72
|
+
...(opts?.laneId ? { 'x-ziggs-lane': opts.laneId } : {}),
|
|
73
|
+
}),
|
|
74
|
+
body: JSON.stringify(input),
|
|
75
|
+
});
|
|
76
|
+
const body = await res.text().catch(() => '');
|
|
77
|
+
if (!res.ok)
|
|
78
|
+
throwApiError(res, body, `requestArtifactAccess failed: ${res.status}`);
|
|
79
|
+
const result = JSON.parse(body);
|
|
80
|
+
if (result.status !== 'pending_approval' || !result.agreementId)
|
|
81
|
+
throw new Error('Expected a pending artifact access request');
|
|
82
|
+
return { status: 'pending_approval', agreementId: result.agreementId };
|
|
83
|
+
}
|
|
84
|
+
async delegateGrant(parentGrantId, input, opts) {
|
|
67
85
|
const res = await fetch(`${this.baseUrl}/context/grants/${encodeURIComponent(parentGrantId)}/delegate`, {
|
|
68
86
|
method: 'POST',
|
|
69
87
|
headers: buildOperatorHeaders(this.operatorKey, this.agentId, {
|
|
70
88
|
'content-type': 'application/json',
|
|
89
|
+
...(opts?.idempotencyKey
|
|
90
|
+
? { 'Idempotency-Key': opts.idempotencyKey }
|
|
91
|
+
: {}),
|
|
71
92
|
}),
|
|
72
93
|
body: JSON.stringify(input),
|
|
73
94
|
});
|
|
@@ -114,11 +135,14 @@ export class ContextGrantsClient {
|
|
|
114
135
|
* is granted immediately; anyone else opens a request the owner approves,
|
|
115
136
|
* exactly like `delegateGrant`'s new-party path.
|
|
116
137
|
*/
|
|
117
|
-
async shareArtifact(artifactId, input) {
|
|
138
|
+
async shareArtifact(artifactId, input, opts) {
|
|
118
139
|
const res = await fetch(`${this.baseUrl}/context/artifacts/${encodeURIComponent(artifactId)}/share`, {
|
|
119
140
|
method: 'POST',
|
|
120
141
|
headers: buildOperatorHeaders(this.operatorKey, this.agentId, {
|
|
121
142
|
'content-type': 'application/json',
|
|
143
|
+
...(opts?.idempotencyKey
|
|
144
|
+
? { 'Idempotency-Key': opts.idempotencyKey }
|
|
145
|
+
: {}),
|
|
122
146
|
}),
|
|
123
147
|
body: JSON.stringify(input),
|
|
124
148
|
});
|
|
@@ -27,7 +27,7 @@ export interface ContextReadQuery {
|
|
|
27
27
|
cursor?: string;
|
|
28
28
|
limit?: number;
|
|
29
29
|
after?: string;
|
|
30
|
-
direction?: 'forward';
|
|
30
|
+
direction?: 'forward' | 'backward';
|
|
31
31
|
state?: string;
|
|
32
32
|
contextGrantId?: string;
|
|
33
33
|
}
|
|
@@ -58,6 +58,13 @@ export interface ContextSnapshotParticipant {
|
|
|
58
58
|
export interface ContextSnapshotResult {
|
|
59
59
|
history: unknown[];
|
|
60
60
|
agreements: unknown[];
|
|
61
|
+
/** Bounded room inventory, without bodies. Absent on older backends. */
|
|
62
|
+
artifacts?: ContextReadEnvelope<{
|
|
63
|
+
artifactId: string;
|
|
64
|
+
name: string | null;
|
|
65
|
+
contentType: string;
|
|
66
|
+
timestamp: string | null;
|
|
67
|
+
}>;
|
|
61
68
|
agents: Array<ContextSnapshotParticipant & {
|
|
62
69
|
agentId: string;
|
|
63
70
|
}>;
|
|
@@ -137,8 +137,20 @@ export type PlanStepPatch = {
|
|
|
137
137
|
status: 'pending' | 'in_progress' | 'completed' | 'skipped';
|
|
138
138
|
result?: unknown;
|
|
139
139
|
};
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Who the holder is waiting on, because its last act was asking them.
|
|
142
|
+
*
|
|
143
|
+
* Rides the progress write because that is when it is known and that is what
|
|
144
|
+
* makes it false again: the server clears it on the next progress write that
|
|
145
|
+
* does not restate it, and on any terminal transition. A holder that did
|
|
146
|
+
* something else after asking is no longer waiting.
|
|
147
|
+
*/
|
|
148
|
+
export type TaskWaitingOn = {
|
|
149
|
+
kind: 'user' | 'agent';
|
|
150
|
+
id: string;
|
|
151
|
+
};
|
|
152
|
+
export declare function updateTaskPlanSteps(taskId: string, patches: PlanStepPatch[], creds: Creds, waitingOn?: TaskWaitingOn | null): Promise<TaskWriteConfirm>;
|
|
153
|
+
export declare function updateTaskPlanStep(taskId: string, stepId: string, status: string, result: unknown, creds: Creds, waitingOn?: TaskWaitingOn | null): Promise<TaskWriteConfirm>;
|
|
142
154
|
export declare function reportTask(taskId: string, message: string | undefined, creds: Creds): Promise<TaskWriteConfirm>;
|
|
143
155
|
export declare function setTaskSatisfaction(taskId: string, satisfaction: 'positive' | 'negative', creds: Creds): Promise<TaskWriteConfirm>;
|
|
144
156
|
export interface ListTasksOptions {
|
package/dist/http/TaskClient.js
CHANGED
|
@@ -277,13 +277,17 @@ export async function replaceTaskPlan(taskId, steps, creds) {
|
|
|
277
277
|
throw new Error('Invalid response: task write confirm not found');
|
|
278
278
|
return confirm;
|
|
279
279
|
}
|
|
280
|
-
export async function updateTaskPlanSteps(taskId, patches, creds) {
|
|
280
|
+
export async function updateTaskPlanSteps(taskId, patches, creds, waitingOn) {
|
|
281
281
|
if (!taskId)
|
|
282
282
|
throw new Error('updateTaskPlanSteps: taskId is required');
|
|
283
|
-
|
|
283
|
+
const named = Array.isArray(patches) ? patches : [];
|
|
284
|
+
// A bare waiting-on write is the ask that produced no other progress, and
|
|
285
|
+
// the route takes it on its own. Without this the only way to say "I asked
|
|
286
|
+
// Dana" was to invent a plan step to tick.
|
|
287
|
+
if (named.length === 0 && !waitingOn) {
|
|
284
288
|
throw new Error('updateTaskPlanSteps: patches must name at least one step');
|
|
285
289
|
}
|
|
286
|
-
for (const patch of
|
|
290
|
+
for (const patch of named) {
|
|
287
291
|
if (!patch?.stepId)
|
|
288
292
|
throw new Error('updateTaskPlanSteps: each patch needs a stepId');
|
|
289
293
|
if (!patch?.status)
|
|
@@ -293,7 +297,10 @@ export async function updateTaskPlanSteps(taskId, patches, creds) {
|
|
|
293
297
|
const res = await fetch(`${getTaskBaseUrl()}/${taskId}/plan`, {
|
|
294
298
|
method: 'PATCH',
|
|
295
299
|
headers: buildHeaders(creds),
|
|
296
|
-
body: JSON.stringify({
|
|
300
|
+
body: JSON.stringify({
|
|
301
|
+
...(named.length > 0 ? { patches: named } : {}),
|
|
302
|
+
...(waitingOn !== undefined ? { waitingOn } : {}),
|
|
303
|
+
}),
|
|
297
304
|
});
|
|
298
305
|
if (!res.ok) {
|
|
299
306
|
const responseBody = await res.text().catch(() => '');
|
|
@@ -305,7 +312,7 @@ export async function updateTaskPlanSteps(taskId, patches, creds) {
|
|
|
305
312
|
throw new Error('Invalid response: task write confirm not found');
|
|
306
313
|
return confirm;
|
|
307
314
|
}
|
|
308
|
-
export async function updateTaskPlanStep(taskId, stepId, status, result, creds) {
|
|
315
|
+
export async function updateTaskPlanStep(taskId, stepId, status, result, creds, waitingOn) {
|
|
309
316
|
if (!taskId)
|
|
310
317
|
throw new Error('updateTaskPlanStep: taskId is required');
|
|
311
318
|
if (!stepId)
|
|
@@ -316,7 +323,15 @@ export async function updateTaskPlanStep(taskId, stepId, status, result, creds)
|
|
|
316
323
|
const res = await fetch(`${getTaskBaseUrl()}/${taskId}/plan`, {
|
|
317
324
|
method: 'PATCH',
|
|
318
325
|
headers: buildHeaders(creds),
|
|
319
|
-
|
|
326
|
+
// One write, not two: the server clears `waitingOn` on a progress write
|
|
327
|
+
// that does not restate it, so posting the tick and the ask separately
|
|
328
|
+
// would set it and then immediately drop it.
|
|
329
|
+
body: JSON.stringify({
|
|
330
|
+
stepId,
|
|
331
|
+
status,
|
|
332
|
+
...(result !== undefined && { result }),
|
|
333
|
+
...(waitingOn !== undefined ? { waitingOn } : {}),
|
|
334
|
+
}),
|
|
320
335
|
});
|
|
321
336
|
if (!res.ok) {
|
|
322
337
|
const responseBody = await res.text().catch(() => '');
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiE
|
|
|
12
12
|
export { ApiError } from './types.js';
|
|
13
13
|
export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, AgreementParties, AgreementPartySide, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, InboxDeliveryKind, InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxRequestRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, InboxPeek, InboxReadOptions, } from './types.js';
|
|
14
14
|
export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, ClaimOptions, } from './http/AgreementClient.js';
|
|
15
|
+
export { decisionWords } from './decisionWords.js';
|
|
16
|
+
export type { DecisionOutcome, DecisionWords, DecisionWordsInput, } from './decisionWords.js';
|
|
15
17
|
export { planInboxAck, planPartialInboxAck, inboxAckAllowed, inboxAckHandledIds, } from './inboxAck.js';
|
|
16
18
|
export type { InboxAckDecision, InboxAckStep, InboxAckEnvelope, PlanInboxAckOptions, } from './inboxAck.js';
|
|
17
19
|
export { agreementLaneId, agreementIdFromLane, isAgreementLaneId, isAgreementId } from '@ziggs-ai/contracts';
|
|
@@ -20,3 +22,5 @@ export { INBOX_HOST_CLAIMANT_HEADER, INSTANCE_IDENTITY_MAX_LENGTH, cliHostIdenti
|
|
|
20
22
|
export * from './utils/appUrls.js';
|
|
21
23
|
export { sessionOrientation, } from './sessionOrientation.js';
|
|
22
24
|
export type { ContinuationKind, InboxAckDriver, InboxHandling, RepresentedPerson, SessionContinuation, SessionOrientation, SessionOrientationInput, } from './sessionOrientation.js';
|
|
25
|
+
export { inboxEngagement, isEchoOrDuplicate, isHireWorkOrder, contactPath, formationGuide, marketplaceFormation, liveHireChangedTerms, liveHireConflictFromStatus, withdrawHeldGraph, reminderHonesty, graphReleaseHonesty, hintsFromTasks, } from './engagementGuide.js';
|
|
26
|
+
export type { EngagementGuide, EngagementGuideKind, EngagementOutcome, EngagementTaskHint, ContactPathInput, FormationInput, GraphHonesty, HireTermsHonesty, WaitingOn, } from './engagementGuide.js';
|
package/dist/index.js
CHANGED
|
@@ -13,9 +13,13 @@ export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, }
|
|
|
13
13
|
// half of them had drifted).
|
|
14
14
|
export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiError.js';
|
|
15
15
|
export { ApiError } from './types.js';
|
|
16
|
+
// One decision, one sentence, on every rail: the reason a delivery row carries
|
|
17
|
+
// when somebody answered a request this agent made, rendered as words.
|
|
18
|
+
export { decisionWords } from './decisionWords.js';
|
|
16
19
|
export { planInboxAck, planPartialInboxAck, inboxAckAllowed, inboxAckHandledIds, } from './inboxAck.js';
|
|
17
20
|
export { agreementLaneId, agreementIdFromLane, isAgreementLaneId, isAgreementId } from '@ziggs-ai/contracts';
|
|
18
21
|
export * from './shared/operatorKey.js';
|
|
19
22
|
export { INBOX_HOST_CLAIMANT_HEADER, INSTANCE_IDENTITY_MAX_LENGTH, cliHostIdentity, hostIdentity, instanceIdentity, mcpConnectionIdentity, } from './instanceIdentity.js';
|
|
20
23
|
export * from './utils/appUrls.js';
|
|
21
24
|
export { sessionOrientation, } from './sessionOrientation.js';
|
|
25
|
+
export { inboxEngagement, isEchoOrDuplicate, isHireWorkOrder, contactPath, formationGuide, marketplaceFormation, liveHireChangedTerms, liveHireConflictFromStatus, withdrawHeldGraph, reminderHonesty, graphReleaseHonesty, hintsFromTasks, } from './engagementGuide.js';
|