@ziggs-ai/api-client 0.20.0 → 0.21.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 +39 -0
- package/dist/capabilities/context.d.ts +1 -0
- package/dist/capabilities/context.js +42 -1
- 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/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/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +11 -0
- package/package.json +2 -2
|
@@ -22,3 +22,10 @@ export declare const agreementRequestCapability: CapabilityDefinition;
|
|
|
22
22
|
export declare const agreementOfferCapability: CapabilityDefinition;
|
|
23
23
|
export declare const agreementHandoffCapability: CapabilityDefinition;
|
|
24
24
|
export declare const AGREEMENT_VERB_CAPABILITIES: CapabilityDefinition[];
|
|
25
|
+
/**
|
|
26
|
+
* Subcontract sits next to the propose verbs, not inside them: the parent
|
|
27
|
+
* already exists, and the conversation is optional. The server uses the
|
|
28
|
+
* parent's own chat when none is named. That list of required arguments is
|
|
29
|
+
* this definition — nextCall, MCP, and the SDK tool all read it.
|
|
30
|
+
*/
|
|
31
|
+
export declare const agreementSubcontractCapability: CapabilityDefinition;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAgreement, proposeBroadcast, proposeDirectTo, } from '../http/AgreementClient.js';
|
|
1
|
+
import { delegateAgreement, getAgreement, proposeBroadcast, proposeDirectTo, } from '../http/AgreementClient.js';
|
|
2
2
|
import { publishOffer } from '../http/MarketplaceClient.js';
|
|
3
3
|
import { fullCreds } from './types.js';
|
|
4
4
|
import { nextCall } from './nextCall.js';
|
|
@@ -382,3 +382,86 @@ export const AGREEMENT_VERB_CAPABILITIES = [
|
|
|
382
382
|
agreementOfferCapability,
|
|
383
383
|
agreementHandoffCapability,
|
|
384
384
|
];
|
|
385
|
+
/**
|
|
386
|
+
* Subcontract sits next to the propose verbs, not inside them: the parent
|
|
387
|
+
* already exists, and the conversation is optional. The server uses the
|
|
388
|
+
* parent's own chat when none is named. That list of required arguments is
|
|
389
|
+
* this definition — nextCall, MCP, and the SDK tool all read it.
|
|
390
|
+
*/
|
|
391
|
+
export const agreementSubcontractCapability = {
|
|
392
|
+
key: 'agreement_subcontract',
|
|
393
|
+
names: { sdk: 'agreement_subcontract', mcp: 'ziggs_agreement_subcontract' },
|
|
394
|
+
title: 'Subcontract part of your work',
|
|
395
|
+
descriptions: {
|
|
396
|
+
sdk: 'Delegate a slice of an active parent agreement to another agent. Name the parent, the worker, and the slice. The worker must approve — never impersonated. A chat is optional: omit it and the slice lands in the parent agreement\'s own room.',
|
|
397
|
+
mcp: 'Delegate a slice of an active parent agreement to another agent. Name the parent, the worker, and the slice. The worker must approve — never impersonated. A chat is optional: omit it and the slice lands in the parent agreement\'s own room. Spawn tasks for the worker under the sub-agreement once it is active.',
|
|
398
|
+
},
|
|
399
|
+
annotation: 'write',
|
|
400
|
+
params: {
|
|
401
|
+
parentAgreementId: {
|
|
402
|
+
type: 'string',
|
|
403
|
+
required: true,
|
|
404
|
+
description: 'The active agreement you are delegating under',
|
|
405
|
+
},
|
|
406
|
+
executorId: {
|
|
407
|
+
type: 'string',
|
|
408
|
+
required: true,
|
|
409
|
+
description: 'Agent doing the delegated work',
|
|
410
|
+
},
|
|
411
|
+
description: {
|
|
412
|
+
type: 'string',
|
|
413
|
+
required: true,
|
|
414
|
+
description: 'What the sub-agreement covers',
|
|
415
|
+
},
|
|
416
|
+
chatId: {
|
|
417
|
+
type: 'string',
|
|
418
|
+
description: "Chat the subcontract is coordinated in. Omit it and the server uses the parent agreement's own chat — which is where a slice with no conversation of its own belongs.",
|
|
419
|
+
},
|
|
420
|
+
price: {
|
|
421
|
+
type: 'number',
|
|
422
|
+
description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Recording a price here does not itself move any.',
|
|
423
|
+
},
|
|
424
|
+
lifecycle: {
|
|
425
|
+
type: 'string',
|
|
426
|
+
enum: ['open', 'time-bound', 'count-bound'],
|
|
427
|
+
description: "Usually inferred: expiresAt → 'time-bound', maxExecutions → 'count-bound', neither → 'open' (standing).",
|
|
428
|
+
},
|
|
429
|
+
expiresAt: { type: 'string' },
|
|
430
|
+
maxExecutions: { type: 'number' },
|
|
431
|
+
agreementDescription: { type: 'string' },
|
|
432
|
+
payerId: {
|
|
433
|
+
type: 'string',
|
|
434
|
+
description: 'Who pays for the subcontracted work. Defaults to the delegating side.',
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
needsAgentId: true,
|
|
438
|
+
handler: async (args, env) => {
|
|
439
|
+
const chatId = args['chatId'];
|
|
440
|
+
const agreement = await delegateAgreement({
|
|
441
|
+
parentAgreementId: args['parentAgreementId'],
|
|
442
|
+
executorId: args['executorId'],
|
|
443
|
+
description: args['description'],
|
|
444
|
+
...(typeof chatId === 'string' && chatId.trim()
|
|
445
|
+
? { chatId: chatId.trim() }
|
|
446
|
+
: {}),
|
|
447
|
+
...(args['price'] === undefined ? {} : { price: args['price'] }),
|
|
448
|
+
...(args['lifecycle'] === undefined
|
|
449
|
+
? {}
|
|
450
|
+
: { lifecycle: args['lifecycle'] }),
|
|
451
|
+
...(args['expiresAt'] === undefined
|
|
452
|
+
? {}
|
|
453
|
+
: { expiresAt: args['expiresAt'] }),
|
|
454
|
+
...(args['maxExecutions'] === undefined
|
|
455
|
+
? {}
|
|
456
|
+
: { maxExecutions: args['maxExecutions'] }),
|
|
457
|
+
...(args['agreementDescription'] === undefined
|
|
458
|
+
? {}
|
|
459
|
+
: { agreementDescription: args['agreementDescription'] }),
|
|
460
|
+
...(args['payerId'] === undefined
|
|
461
|
+
? {}
|
|
462
|
+
: { payerId: args['payerId'] }),
|
|
463
|
+
}, fullCreds(env));
|
|
464
|
+
return { agreement };
|
|
465
|
+
},
|
|
466
|
+
sdkOptions: { isAgreementCreation: true },
|
|
467
|
+
};
|
|
@@ -18,7 +18,10 @@ function reportingHint(env, contentType, taskId) {
|
|
|
18
18
|
? `Recorded as a task-bound result artifact. Close the task by setting its terminal result with ${close}.`
|
|
19
19
|
: `Recorded as a result artifact, but not bound to a task — pass taskId to bind it, then close the task with ${close}.`;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
// The record and the delivery are different acts. A chat message
|
|
22
|
+
// is not where the next agent collects the work, and saying that as "chat is
|
|
23
|
+
// conversation only" read as a ban on telling the person who asked.
|
|
24
|
+
return `Reporting finished work? Record it with contentType=result bound to the task (taskId), then close the task with ${close} — that record is what the next agent reads. Telling the person who asked is a separate chat message, and still worth sending.`;
|
|
22
25
|
}
|
|
23
26
|
/** Inline body cap + escape hatch, shared by SDK/MCP descriptions. */
|
|
24
27
|
const ARTIFACT_RECORD_INLINE_CAP = 'Inline text max 50000 characters. Over that: pass the body as a file ' +
|
|
@@ -269,8 +272,8 @@ export const listArtifactsCapability = {
|
|
|
269
272
|
names: { sdk: 'artifact_list', mcp: 'ziggs_artifact_list' },
|
|
270
273
|
title: 'List artifacts you wrote',
|
|
271
274
|
descriptions: {
|
|
272
|
-
sdk: 'List artifacts you authored, in any scope or none — including free-standing ones no scope read can reach. Forward-delta with `after`.',
|
|
273
|
-
mcp: 'List artifacts YOU authored, across every scope and none. Use this to find something you ' +
|
|
275
|
+
sdk: 'List business artifacts you authored, in any scope or none — including free-standing ones no scope read can reach. Tool-operation and thought traces are excluded. Forward-delta with `after`.',
|
|
276
|
+
mcp: 'List business artifacts YOU authored, across every scope and none; tool-operation and thought traces are excluded. Use this to find something you ' +
|
|
274
277
|
'recorded free-standing (no chat/agreement/task), which the scope reads cannot return. ' +
|
|
275
278
|
'To read an artifact someone shared WITH you, use ziggs_context_read with via=artifact:<id>; ' +
|
|
276
279
|
'to see what has been shared with you, use ziggs_grant_list with scopeKind=artifact.',
|
|
@@ -299,8 +302,27 @@ export const listArtifactsCapability = {
|
|
|
299
302
|
};
|
|
300
303
|
const UNSEARCHED_NOTE = 'These were NOT searched. You hold no grant on them, so only their label is ' +
|
|
301
304
|
'visible here — no contents, no message counts, nothing that would leak what ' +
|
|
302
|
-
'is inside. Each row carries requestAccess:
|
|
303
|
-
'
|
|
305
|
+
'is inside. Each row carries requestAccess: an access explanation, not a ' +
|
|
306
|
+
'root grant. A human issues the grant; this never widens the scope to the ' +
|
|
307
|
+
'chat, agreement, or org around one artifact.';
|
|
308
|
+
const OPEN_ID_FIELD = {
|
|
309
|
+
chat: 'chatId',
|
|
310
|
+
agreement: 'agreementId',
|
|
311
|
+
artifact: 'artifactId',
|
|
312
|
+
task: 'taskId',
|
|
313
|
+
};
|
|
314
|
+
function pendingAccessReceipt(result, extra) {
|
|
315
|
+
return {
|
|
316
|
+
...extra,
|
|
317
|
+
ok: false,
|
|
318
|
+
outcome: 'pending',
|
|
319
|
+
status: 'pending_approval',
|
|
320
|
+
state: 'requested',
|
|
321
|
+
recoverable: true,
|
|
322
|
+
agreementId: result.agreementId,
|
|
323
|
+
ownerId: result.ownerId,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
304
326
|
/**
|
|
305
327
|
* The locked scopes near a search that found nothing.
|
|
306
328
|
*
|
|
@@ -329,19 +351,18 @@ async function unsearchedContext(env) {
|
|
|
329
351
|
};
|
|
330
352
|
}
|
|
331
353
|
return {
|
|
332
|
-
items: items.map((item) =>
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
})),
|
|
354
|
+
items: items.map((item) => {
|
|
355
|
+
const idField = OPEN_ID_FIELD[item.scopeRef.kind];
|
|
356
|
+
return {
|
|
357
|
+
...item,
|
|
358
|
+
requestAccess: nextCall(env, 'access_explain', idField ? { [idField]: item.scopeRef.id } : undefined, idField
|
|
359
|
+
? `explain access to locked ${item.type} "${item.label}" — a human issues a root grant; this call does not request or widen`
|
|
360
|
+
: `locked ${item.type} "${item.label}" cannot be requested from this surface — a human issues a root grant; this never widens the scope`, {
|
|
361
|
+
hold: 'human_approval',
|
|
362
|
+
...(idField ? {} : { outcome: 'unavailable' }),
|
|
363
|
+
}),
|
|
364
|
+
};
|
|
365
|
+
}),
|
|
345
366
|
count: items.length,
|
|
346
367
|
note: UNSEARCHED_NOTE,
|
|
347
368
|
};
|
|
@@ -366,10 +387,10 @@ export const findArtifactsCapability = {
|
|
|
366
387
|
names: { sdk: 'artifact_find', mcp: 'ziggs_artifact_find' },
|
|
367
388
|
title: 'Find artifacts you can read',
|
|
368
389
|
descriptions: {
|
|
369
|
-
sdk: 'Search artifacts you can already reach — what you authored, what your readable chats/agreements/tasks hold, and what artifact grants you hold — by words in the name, filename or body. Bounded by your active lane and the rooms this wake is shared into, the same fences a point read obeys; there is no cross-customer arm and narrowing filters are refused, not ignored. Every answer carries coverage (fields searched, reach arms and their sizes, any that hit a cap), so an empty result means no match in what was searched, never that the thing does not exist. When nothing matched you also get notSearched: labels of context you hold no grant on, with
|
|
370
|
-
mcp: 'Search the artifacts YOU can read for words in their name, filename or body. Covers what you authored, what the chats/agreements/tasks you can read contain, and artifacts shared with you — bounded by your active lane and shared-room containment, exactly like a point read. Open a hit with ziggs_context_read (via=artifact:<id>). ' +
|
|
390
|
+
sdk: 'Search business artifacts you can already reach (excluding tool-operation and thought traces) — what you authored, what your readable chats/agreements/tasks hold, and what artifact grants you hold — by words in the name, filename or body. Bounded by your active lane and the rooms this wake is shared into, the same fences a point read obeys; there is no cross-customer arm and narrowing filters are refused, not ignored. Every answer carries coverage (fields searched, reach arms and their sizes, any that hit a cap), so an empty result means no match in what was searched, never that the thing does not exist. When nothing matched you also get notSearched: labels of context you hold no grant on, each with an access explanation — a human issues a root grant; this never widens. Metadata only, never contents.',
|
|
391
|
+
mcp: 'Search the business artifacts YOU can read for words in their name, filename or body; tool-operation and thought traces are excluded. Covers what you authored, what the chats/agreements/tasks you can read contain, and artifacts shared with you — bounded by your active lane and shared-room containment, exactly like a point read. Open a hit with ziggs_context_read (via=artifact:<id>). ' +
|
|
371
392
|
'Read the coverage block before concluding anything: it names the fields searched, the arms that produced candidates and any that hit a cap. No match means no match in THOSE sources — it is never a statement that the artifact does not exist. ' +
|
|
372
|
-
'If nothing matched, notSearched lists context you cannot read yet (same rows as ziggs_context_discover_grantable): labels only, never contents, each with
|
|
393
|
+
'If nothing matched, notSearched lists context you cannot read yet (same rows as ziggs_context_discover_grantable): labels only, never contents, each with an access explanation. A human issues a root grant; do not treat that as a share or a wider container grant. Do not report "not found" while notSearched is non-empty — say what you searched and what is still locked.',
|
|
373
394
|
},
|
|
374
395
|
annotation: 'read-only',
|
|
375
396
|
params: {
|
|
@@ -446,6 +467,10 @@ export const shareArtifactCapability = {
|
|
|
446
467
|
type: 'string',
|
|
447
468
|
description: 'Chat to surface the approval request in, when the receiver’s owner has to approve',
|
|
448
469
|
},
|
|
470
|
+
idempotencyKey: {
|
|
471
|
+
type: 'string',
|
|
472
|
+
description: 'Replay the same share. Same key and payload recover the original request; the same key with a different payload is a conflict, not a second share.',
|
|
473
|
+
},
|
|
449
474
|
},
|
|
450
475
|
needsAgentId: true,
|
|
451
476
|
handler: async (args, env) => {
|
|
@@ -460,14 +485,16 @@ export const shareArtifactCapability = {
|
|
|
460
485
|
holderId,
|
|
461
486
|
expiresAt: args['expiresAt'],
|
|
462
487
|
chatId: args['chatId'],
|
|
488
|
+
}, {
|
|
489
|
+
idempotencyKey: typeof args['idempotencyKey'] === 'string'
|
|
490
|
+
? args['idempotencyKey']
|
|
491
|
+
: undefined,
|
|
463
492
|
});
|
|
464
493
|
if (result.status === 'pending_approval') {
|
|
465
|
-
return {
|
|
466
|
-
ok: false,
|
|
467
|
-
outcome: 'pending',
|
|
494
|
+
return pendingAccessReceipt(result, {
|
|
468
495
|
...result,
|
|
469
|
-
note: `
|
|
470
|
-
};
|
|
496
|
+
note: `Requested, not granted — ${result.ownerId ?? 'the owner'} must approve agreement ${result.agreementId}. Recover that id; do not open a second card.`,
|
|
497
|
+
});
|
|
471
498
|
}
|
|
472
499
|
return {
|
|
473
500
|
ok: true,
|
|
@@ -1,4 +1,34 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type SendChatMessageResult } from '../http/ChatClient.js';
|
|
2
|
+
import { type CapabilityDefinition, type CapabilityEnv } from './types.js';
|
|
3
|
+
import { type WorkContext } from './nextCall.js';
|
|
4
|
+
export interface SendPresentation {
|
|
5
|
+
ok: true;
|
|
6
|
+
chatId: string;
|
|
7
|
+
messageId: string;
|
|
8
|
+
/** What the server did with this send, stated so it cannot be over-read. */
|
|
9
|
+
delivered: string;
|
|
10
|
+
/** Who it was addressed to, exactly as the server resolved it. */
|
|
11
|
+
to?: {
|
|
12
|
+
id: string;
|
|
13
|
+
type: string;
|
|
14
|
+
};
|
|
15
|
+
workContext?: WorkContext;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Present a send as what it is: a message that now exists, addressed to
|
|
19
|
+
* somebody, which may or may not wake anyone.
|
|
20
|
+
*
|
|
21
|
+
* An agent that sent an answer and read `success: true` had every reason to
|
|
22
|
+
* report the answer delivered. Three different things hide behind that word.
|
|
23
|
+
* The message exists — that much is certain. Who it reached is the server's
|
|
24
|
+
* decision, not the caller's, because an omitted recipient may be inferred.
|
|
25
|
+
* Mailbox routing is a separate asynchronous decision, including coverage by
|
|
26
|
+
* an assistant when the addressee is a person.
|
|
27
|
+
*
|
|
28
|
+
* A server that does not report the destination gets one honest line about the
|
|
29
|
+
* message existing, and nothing invented about where it went.
|
|
30
|
+
*/
|
|
31
|
+
export declare function presentSendResult(result: SendChatMessageResult, env: CapabilityEnv): SendPresentation;
|
|
2
32
|
/**
|
|
3
33
|
* the decided chat surface for agents: chat_open only.
|
|
4
34
|
* There is deliberately NO chat-listing tool on the SDK: taking part in a room
|
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
import { openConversation } from '../http/ChatClient.js';
|
|
2
2
|
import { fullCreds } from './types.js';
|
|
3
|
+
import { workContextFromEnv } from './nextCall.js';
|
|
4
|
+
/**
|
|
5
|
+
* Present a send as what it is: a message that now exists, addressed to
|
|
6
|
+
* somebody, which may or may not wake anyone.
|
|
7
|
+
*
|
|
8
|
+
* An agent that sent an answer and read `success: true` had every reason to
|
|
9
|
+
* report the answer delivered. Three different things hide behind that word.
|
|
10
|
+
* The message exists — that much is certain. Who it reached is the server's
|
|
11
|
+
* decision, not the caller's, because an omitted recipient may be inferred.
|
|
12
|
+
* Mailbox routing is a separate asynchronous decision, including coverage by
|
|
13
|
+
* an assistant when the addressee is a person.
|
|
14
|
+
*
|
|
15
|
+
* A server that does not report the destination gets one honest line about the
|
|
16
|
+
* message existing, and nothing invented about where it went.
|
|
17
|
+
*/
|
|
18
|
+
export function presentSendResult(result, env) {
|
|
19
|
+
const workContext = workContextFromEnv(env);
|
|
20
|
+
const delivery = result.delivery;
|
|
21
|
+
const base = {
|
|
22
|
+
ok: true,
|
|
23
|
+
chatId: result.chatId,
|
|
24
|
+
messageId: result.messageId,
|
|
25
|
+
...(workContext ? { workContext } : {}),
|
|
26
|
+
};
|
|
27
|
+
if (!delivery) {
|
|
28
|
+
return {
|
|
29
|
+
...base,
|
|
30
|
+
delivered: `Message ${result.messageId} was accepted into chat ${result.chatId}. Accepted is not read: nothing here says anyone has seen it.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const destination = delivery.broadcast
|
|
34
|
+
? 'the humans in the room, not one addressee'
|
|
35
|
+
: `${delivery.to.id} (${delivery.to.type})`;
|
|
36
|
+
return {
|
|
37
|
+
...base,
|
|
38
|
+
delivered: `Message ${result.messageId} was accepted into chat ${result.chatId}, addressed to ${destination}. Mailbox routing is unconfirmed; a person may be covered by an assistant. Accepted is not read and does not confirm a wake or reply.`,
|
|
39
|
+
to: delivery.to,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
3
42
|
/**
|
|
4
43
|
* the decided chat surface for agents: chat_open only.
|
|
5
44
|
* There is deliberately NO chat-listing tool on the SDK: taking part in a room
|
|
@@ -23,3 +23,4 @@ export declare const contextDelegateCapability: CapabilityDefinition;
|
|
|
23
23
|
export declare const openCapability: CapabilityDefinition;
|
|
24
24
|
export declare const accessExplainCapability: CapabilityDefinition;
|
|
25
25
|
export declare const CONTEXT_CAPABILITIES: CapabilityDefinition[];
|
|
26
|
+
export declare const contextRequestCapability: CapabilityDefinition;
|
|
@@ -195,6 +195,10 @@ export const contextDelegateCapability = {
|
|
|
195
195
|
type: 'string',
|
|
196
196
|
description: 'from-now watermark ISO-8601 (optional; server may default)',
|
|
197
197
|
},
|
|
198
|
+
idempotencyKey: {
|
|
199
|
+
type: 'string',
|
|
200
|
+
description: 'Replay the same delegate. Same key and payload recover the original request; the same key with a different payload is a conflict, not a second card.',
|
|
201
|
+
},
|
|
198
202
|
},
|
|
199
203
|
needsAgentId: true,
|
|
200
204
|
handler: async (args, env) => {
|
|
@@ -222,12 +226,18 @@ export const contextDelegateCapability = {
|
|
|
222
226
|
temporal: temporal,
|
|
223
227
|
expiresAt: args['expiresAt'] ?? undefined,
|
|
224
228
|
watermarkAt: args['watermarkAt'],
|
|
229
|
+
}, {
|
|
230
|
+
idempotencyKey: typeof args['idempotencyKey'] === 'string'
|
|
231
|
+
? args['idempotencyKey']
|
|
232
|
+
: undefined,
|
|
225
233
|
});
|
|
226
234
|
if (result.status === 'pending_approval') {
|
|
227
235
|
return {
|
|
228
236
|
status: 'pending_approval',
|
|
237
|
+
state: 'requested',
|
|
229
238
|
outcome: 'pending',
|
|
230
|
-
|
|
239
|
+
recoverable: true,
|
|
240
|
+
message: "Requested, not granted — this grant's original owner must approve. Recover agreementId; do not treat this as a share and do not widen the scope.",
|
|
231
241
|
parentGrantId: args['parentGrantId'],
|
|
232
242
|
agreementId: result.agreementId,
|
|
233
243
|
ownerId: result.ownerId,
|
|
@@ -329,3 +339,34 @@ export const CONTEXT_CAPABILITIES = [
|
|
|
329
339
|
contextExpandReachCapability,
|
|
330
340
|
contextDiscoverGrantableCapability,
|
|
331
341
|
];
|
|
342
|
+
export const contextRequestCapability = {
|
|
343
|
+
key: 'context_request',
|
|
344
|
+
names: { sdk: 'context_request', mcp: 'ziggs_context_request' },
|
|
345
|
+
title: 'Ask an owner for artifact access',
|
|
346
|
+
descriptions: {
|
|
347
|
+
sdk: 'Ask a human owner for temporary read-only access to one artifact you were given a reference for. Use your existing chat with that owner. Creates an approval agreement; gives no access until the owner approves. You are the only recipient. After approval, open the original artifact. Never use a generic service agreement as a substitute for this access request.',
|
|
348
|
+
mcp: 'Request access to a restricted artifact directly from its human owner. Pass the artifact id you were given, your conversation with the owner, and the owner account id or participant reference shown in that chat. This creates a pending approval agreement, NOT a grant. No prior grant is needed. The human approves at the agreement link. After approval, ziggs_open the original artifact. Scope is read-only, one artifact, for 1–168 hours (default 24); no other artifacts or chats. Include the intended summary recipient in reason. Keep polling for the decision when asked to wait. A request does not confirm that the artifact exists or belongs to the named owner; ownership is checked at approval.',
|
|
349
|
+
},
|
|
350
|
+
annotation: 'write', needsAgentId: true,
|
|
351
|
+
params: {
|
|
352
|
+
artifactId: { type: 'string', required: true },
|
|
353
|
+
chatId: { type: 'string', required: true },
|
|
354
|
+
ownerId: { type: 'string', required: true, description: 'Human account id or room participant reference, not a persona face' },
|
|
355
|
+
reason: { type: 'string', required: true, description: 'Purpose, including whom you will summarize the artifact to' },
|
|
356
|
+
durationHours: { type: 'number', description: 'Integer 1–168 hours after approval; default 24' },
|
|
357
|
+
idempotencyKey: { type: 'string', description: 'Reuse for the same request; do not create duplicate approval cards' },
|
|
358
|
+
},
|
|
359
|
+
handler: async (args, env) => {
|
|
360
|
+
for (const key of ['artifactId', 'chatId', 'ownerId', 'reason'])
|
|
361
|
+
if (typeof args[key] !== 'string' || !String(args[key]).trim())
|
|
362
|
+
throw new Error(`${key} is required`);
|
|
363
|
+
const durationHours = args.durationHours === undefined ? 24 : Number(args.durationHours);
|
|
364
|
+
if (!Number.isInteger(durationHours) || durationHours < 1 || durationHours > 168)
|
|
365
|
+
throw new Error('durationHours must be an integer between 1 and 168');
|
|
366
|
+
const creds = fullCreds(env);
|
|
367
|
+
const result = await new ContextGrantsClient(creds.operatorKey, creds.agentId, env.baseUrl).requestArtifactAccess(args.artifactId, {
|
|
368
|
+
chatId: args.chatId, ownerId: args.ownerId, reason: args.reason, durationHours,
|
|
369
|
+
}, { idempotencyKey: args.idempotencyKey, laneId: creds.laneId });
|
|
370
|
+
return { ...result, outcome: 'pending', state: 'requested', appUrl: `${(env.webUrl ?? 'https://ziggsai.com').replace(/\/$/, '')}/app/agreements/${encodeURIComponent(result.agreementId)}`, note: 'Waiting for the artifact owner. No access granted yet; open the original artifact only after approval.' };
|
|
371
|
+
},
|
|
372
|
+
};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
export { type CapabilitySurface, type CapabilityAnnotation, type CapabilityParam, type CapabilityEnv, type CapabilityDefinition, fullCreds, rethrowWithContext, } from './types.js';
|
|
2
2
|
export { nextCall, nextCallsFromUntrustedContent, isStaleAdvertisedAction, peerPrincipalId, isPersonaFace, workContextFromEnv, type NextCall, type NextCallOutcome, type NextCallEffects, type NextCallHold, type NextCallOptions, type WorkContext, } from './nextCall.js';
|
|
3
|
-
export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, } from './agreementVerbs.js';
|
|
3
|
+
export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, agreementSubcontractCapability, } from './agreementVerbs.js';
|
|
4
4
|
export { PAYMENT_CAPABILITIES, paymentBalanceCapability } from './payments.js';
|
|
5
5
|
export { LINK_CAPABILITIES, listLinksCapability, proposeLinkCapability, linkIsReachOnly, } from './links.js';
|
|
6
6
|
export { INTRODUCTION_CAPABILITIES, mintIntroductionCapability, redeemIntroductionCapability, listIntroductionsCapability, revokeIntroductionCapability, } from './introductions.js';
|
|
7
7
|
export { AGREEMENT_CAPABILITIES, agreementClaimCapability, presentClaimResult, } from './agreements.js';
|
|
8
8
|
export { LIST_FIELDS_PARAM, parseListFields, pickListedRow, pickListedRows, } from './listedFields.js';
|
|
9
|
-
export { TASK_CAPABILITIES, listTasksCapability } from './tasks.js';
|
|
9
|
+
export { TASK_CAPABILITIES, listTasksCapability, cancelTaskCapability, presentTaskOutcome, type TaskOutcomeEffects, type TaskOutcomePresentation, } from './tasks.js';
|
|
10
10
|
export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
|
|
11
11
|
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
12
12
|
export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
|
|
13
|
-
export { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
|
|
13
|
+
export { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextRequestCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
|
|
14
14
|
export { CONNECTION_CAPABILITIES, connectionProxyCapability, requestConnectionCapability, } from './connections.js';
|
|
15
15
|
export { DISCOVERY_CAPABILITIES, agentSearchCapability, agentGetCapability, } from './discovery.js';
|
|
16
16
|
export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, type UnsearchedContext, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
|
|
17
|
-
export { CHAT_CAPABILITIES, openConversationCapability } from './chat.js';
|
|
17
|
+
export { CHAT_CAPABILITIES, openConversationCapability, presentSendResult, type SendPresentation, } from './chat.js';
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
export { fullCreds, rethrowWithContext, } from './types.js';
|
|
2
2
|
export { nextCall, nextCallsFromUntrustedContent, isStaleAdvertisedAction, peerPrincipalId, isPersonaFace, workContextFromEnv, } from './nextCall.js';
|
|
3
|
-
export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, } from './agreementVerbs.js';
|
|
3
|
+
export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, agreementSubcontractCapability, } from './agreementVerbs.js';
|
|
4
4
|
export { PAYMENT_CAPABILITIES, paymentBalanceCapability } from './payments.js';
|
|
5
5
|
export { LINK_CAPABILITIES, listLinksCapability, proposeLinkCapability, linkIsReachOnly, } from './links.js';
|
|
6
6
|
export { INTRODUCTION_CAPABILITIES, mintIntroductionCapability, redeemIntroductionCapability, listIntroductionsCapability, revokeIntroductionCapability, } from './introductions.js';
|
|
7
7
|
export { AGREEMENT_CAPABILITIES, agreementClaimCapability, presentClaimResult, } from './agreements.js';
|
|
8
8
|
export { LIST_FIELDS_PARAM, parseListFields, pickListedRow, pickListedRows, } from './listedFields.js';
|
|
9
|
-
export { TASK_CAPABILITIES, listTasksCapability } from './tasks.js';
|
|
9
|
+
export { TASK_CAPABILITIES, listTasksCapability, cancelTaskCapability, presentTaskOutcome, } from './tasks.js';
|
|
10
10
|
export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
|
|
11
11
|
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
12
12
|
export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
|
|
13
|
-
export { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
|
|
13
|
+
export { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextRequestCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
|
|
14
14
|
export { CONNECTION_CAPABILITIES, connectionProxyCapability, requestConnectionCapability, } from './connections.js';
|
|
15
15
|
export { DISCOVERY_CAPABILITIES, agentSearchCapability, agentGetCapability, } from './discovery.js';
|
|
16
16
|
export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
|
|
17
|
-
export { CHAT_CAPABILITIES, openConversationCapability } from './chat.js';
|
|
17
|
+
export { CHAT_CAPABILITIES, openConversationCapability, presentSendResult, } from './chat.js';
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import { marketplaceFormation } from '../engagementGuide.js';
|
|
1
2
|
import { pullOffers, pullRequests } from '../http/MarketplaceClient.js';
|
|
2
3
|
import { fullCreds } from './types.js';
|
|
3
4
|
import { nextCall } from './nextCall.js';
|
|
4
5
|
import { LIST_FIELDS_PARAM, parseListFields, pickListedRows } from './listedFields.js';
|
|
5
6
|
const VIEW_KINDS = ['all', 'requests', 'offers'];
|
|
6
7
|
function publishHint(env) {
|
|
7
|
-
const propose = env.surface === 'mcp' ? 'ziggs_agreement_request' : 'agreement_request';
|
|
8
8
|
const claim = env.surface === 'mcp' ? 'ziggs_agreement_claim' : 'agreement_claim';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const publish = env.surface === 'mcp'
|
|
10
|
+
? 'Publish a request with ziggs_agreement_request (claimer works, you pay) or a standing offer with ziggs_agreement_offer (you work, claimer pays).'
|
|
11
|
+
: 'Publish your own via agreement_propose with proposedTo "everyone"/"org" (providerId = your id for an offer, omitted for a request).';
|
|
12
|
+
return (`Reuse an active agreement that already covers the work. Otherwise claim any row with ${claim} ` +
|
|
13
|
+
`(agreementId) — listings are take-it-or-leave-it, never counter one. ${publish}`);
|
|
12
14
|
}
|
|
13
15
|
/** A broadcast sentinel in a party slot means "open", not a counterparty. */
|
|
14
16
|
const BROADCASTS = new Set(['everyone', 'org']);
|
|
@@ -98,6 +100,10 @@ export const marketplaceViewCapability = {
|
|
|
98
100
|
const fields = parseListFields(args['fields']);
|
|
99
101
|
const requestRows = pickListedRows(requests.map((q) => toListingRow(q, 'request')), fields);
|
|
100
102
|
const offerRows = pickListedRows(offers.map((o) => toListingRow(o, 'offer')), fields);
|
|
103
|
+
const listingAgreementIds = [...requests, ...offers]
|
|
104
|
+
.map((row) => row.agreementId)
|
|
105
|
+
.filter((id) => typeof id === 'string' && id.length > 0);
|
|
106
|
+
const engagement = marketplaceFormation(env, { listingAgreementIds });
|
|
101
107
|
return {
|
|
102
108
|
...(kind !== 'offers'
|
|
103
109
|
? { requests: requestRows, requestCount: requests.length }
|
|
@@ -105,13 +111,16 @@ export const marketplaceViewCapability = {
|
|
|
105
111
|
...(kind !== 'requests'
|
|
106
112
|
? { offers: offerRows, offerCount: offers.length }
|
|
107
113
|
: {}),
|
|
114
|
+
engagement,
|
|
108
115
|
// Claiming is the move after browsing, and the id is in the row the
|
|
109
116
|
// caller just received. The prose hint stays for the publish side, which
|
|
110
117
|
// is a choice rather than a call.
|
|
111
118
|
readPlan: [
|
|
112
|
-
...(
|
|
119
|
+
...(listingAgreementIds.length
|
|
113
120
|
? [
|
|
114
|
-
nextCall(env, 'agreement_claim',
|
|
121
|
+
nextCall(env, 'agreement_claim', listingAgreementIds.length === 1
|
|
122
|
+
? { agreementId: listingAgreementIds[0] }
|
|
123
|
+
: undefined, 'claim a listing from this view by passing its agreementId — listings are take-it-or-leave-it, never countered'),
|
|
115
124
|
]
|
|
116
125
|
: []),
|
|
117
126
|
],
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
* and must never be offered here — chat_open / chat_send refuse it, and the
|
|
4
4
|
* baseline's provider learned that only by being refused.
|
|
5
5
|
*/
|
|
6
|
-
const ADDRESS_FIELDS = [
|
|
6
|
+
const ADDRESS_FIELDS = [
|
|
7
|
+
'participantId',
|
|
8
|
+
'to',
|
|
9
|
+
'receiverId',
|
|
10
|
+
'counterparty',
|
|
11
|
+
'executorId',
|
|
12
|
+
];
|
|
7
13
|
/**
|
|
8
14
|
* Required arguments for the capability keys this helper is asked to emit.
|
|
9
15
|
* Kept next to the HTTP bindings so a ready call cannot advertise a schema
|
|
@@ -15,9 +21,18 @@ const REQUIRED_ARGS = {
|
|
|
15
21
|
chat_open: ['participantId'],
|
|
16
22
|
chat_send: ['chatId'],
|
|
17
23
|
agreement_claim: ['agreementId'],
|
|
24
|
+
agreement_get: ['agreementId'],
|
|
25
|
+
agreement_buy: ['counterparty', 'description'],
|
|
26
|
+
agreement_bid: ['counterparty', 'description'],
|
|
27
|
+
agreement_request: ['audience', 'description'],
|
|
28
|
+
agreement_subcontract: ['parentAgreementId', 'executorId', 'description'],
|
|
18
29
|
agreement_respond: ['agreementId'],
|
|
19
30
|
introduction_redeem: ['token'],
|
|
20
31
|
context_issue_grant: ['holderId', 'scopeKind', 'scopeId'],
|
|
32
|
+
artifact_share: ['artifactId', 'holderId'],
|
|
33
|
+
context_delegate: ['parentGrantId', 'holderId', 'scopeKind', 'scopeId', 'temporal'],
|
|
34
|
+
task_create: ['agreementId', 'description'],
|
|
35
|
+
task_cancel: ['taskId'],
|
|
21
36
|
};
|
|
22
37
|
/**
|
|
23
38
|
* Published HTTP invocation per capability key. Paths match the api-client
|
|
@@ -31,17 +46,31 @@ const HTTP_BINDINGS = {
|
|
|
31
46
|
introduction_mint: { method: 'POST', path: '/introductions' },
|
|
32
47
|
introduction_redeem: { method: 'POST', path: '/introductions/:token/redeem' },
|
|
33
48
|
agreement_claim: { method: 'POST', path: '/agreements/:agreementId/claim' },
|
|
49
|
+
agreement_get: { method: 'GET', path: '/agreements/:agreementId' },
|
|
50
|
+
agreement_buy: { method: 'POST', path: '/agreements/proposals' },
|
|
51
|
+
agreement_bid: { method: 'POST', path: '/agreements/proposals' },
|
|
52
|
+
agreement_request: { method: 'POST', path: '/agreements/proposals' },
|
|
53
|
+
agreement_subcontract: {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
path: '/agreements/:parentAgreementId/delegations',
|
|
56
|
+
},
|
|
34
57
|
agreement_respond: {
|
|
35
58
|
method: 'PUT',
|
|
36
59
|
path: '/agreements/:agreementId/approvals/:partyId',
|
|
37
60
|
},
|
|
38
61
|
marketplace_view: { method: 'GET', path: '/marketplace/offers' },
|
|
39
62
|
context_issue_grant: { method: 'POST', path: '/context/grants' },
|
|
63
|
+
artifact_share: { method: 'POST', path: '/context/artifacts/:artifactId/share' },
|
|
64
|
+
context_delegate: { method: 'POST', path: '/context/grants/:parentGrantId/delegate' },
|
|
40
65
|
open: { method: 'POST', path: '/context/open' },
|
|
41
66
|
access_explain: { method: 'POST', path: '/context/access/explain' },
|
|
42
67
|
inbox_peek: { method: 'GET', path: '/inbox/peek' },
|
|
68
|
+
task_get: { method: 'GET', path: '/tasks/:taskId' },
|
|
69
|
+
task_list: { method: 'GET', path: '/tasks' },
|
|
43
70
|
link_list: { method: 'GET', path: '/agreements?engagementKind=link' },
|
|
44
71
|
link_propose: { method: 'POST', path: '/agreements/links' },
|
|
72
|
+
task_create: { method: 'POST', path: '/tasks' },
|
|
73
|
+
task_cancel: { method: 'PATCH', path: '/tasks/:taskId/cancel' },
|
|
45
74
|
};
|
|
46
75
|
/**
|
|
47
76
|
* Intentional surface exclusions. A ready next call has to exist as a tool
|
|
@@ -50,8 +79,15 @@ const HTTP_BINDINGS = {
|
|
|
50
79
|
*/
|
|
51
80
|
const SURFACE_EXPOSURE = {
|
|
52
81
|
context_issue_grant: ['mcp'],
|
|
82
|
+
// The hosted SDK surface enumerates its own work with task_list and reads a
|
|
83
|
+
// task from the wake it arrived on; ziggs_task_get is the MCP delegate's
|
|
84
|
+
// one-task read. Naming it to an SDK agent would offer a tool it cannot call.
|
|
85
|
+
task_get: ['mcp'],
|
|
53
86
|
};
|
|
54
87
|
const MATERIAL_EFFECTS = {
|
|
88
|
+
task_get: {
|
|
89
|
+
disclosure: 'reads back what was recorded; it changes nothing',
|
|
90
|
+
},
|
|
55
91
|
agreement_claim: {
|
|
56
92
|
commitment: 'claiming makes you a party to the posted terms',
|
|
57
93
|
relationship: 'you become the open party on that broadcast',
|
|
@@ -71,6 +107,26 @@ const MATERIAL_EFFECTS = {
|
|
|
71
107
|
chat_open: {
|
|
72
108
|
disclosure: 'opening a room issues the other side a grant on it',
|
|
73
109
|
},
|
|
110
|
+
task_create: {
|
|
111
|
+
commitment: 'creates a task under the named agreement',
|
|
112
|
+
},
|
|
113
|
+
task_cancel: {
|
|
114
|
+
commitment: 'cancel ends this task; a held graph withdraws by cancelling the root',
|
|
115
|
+
},
|
|
116
|
+
agreement_buy: {
|
|
117
|
+
commitment: 'proposes that they work and your side pays',
|
|
118
|
+
relationship: 'a named counterparty — not a listing claim',
|
|
119
|
+
},
|
|
120
|
+
agreement_bid: {
|
|
121
|
+
commitment: 'proposes that you work and they pay',
|
|
122
|
+
},
|
|
123
|
+
agreement_request: {
|
|
124
|
+
commitment: 'posts work anyone may claim; you pay the claimer',
|
|
125
|
+
},
|
|
126
|
+
agreement_subcontract: {
|
|
127
|
+
commitment: 'opens a sub-agreement under the parent; the worker must approve — never impersonated',
|
|
128
|
+
relationship: 'the slice sits under the parent you already hold',
|
|
129
|
+
},
|
|
74
130
|
};
|
|
75
131
|
/** A persona face (`psn_…`) is display state, not a message address. */
|
|
76
132
|
export function isPersonaFace(id) {
|
|
@@ -155,6 +211,7 @@ export function nextCall(env, capabilityKey, args, why, opts) {
|
|
|
155
211
|
};
|
|
156
212
|
}
|
|
157
213
|
const missing = [];
|
|
214
|
+
const strippedFaces = [];
|
|
158
215
|
const filled = {};
|
|
159
216
|
for (const [key, value] of Object.entries(args ?? {})) {
|
|
160
217
|
if (!isFilled(value))
|
|
@@ -162,6 +219,7 @@ export function nextCall(env, capabilityKey, args, why, opts) {
|
|
|
162
219
|
if (ADDRESS_FIELDS.includes(key) &&
|
|
163
220
|
isPersonaFace(value)) {
|
|
164
221
|
missing.push(key);
|
|
222
|
+
strippedFaces.push(key);
|
|
165
223
|
continue;
|
|
166
224
|
}
|
|
167
225
|
filled[key] = value;
|
|
@@ -183,11 +241,11 @@ export function nextCall(env, capabilityKey, args, why, opts) {
|
|
|
183
241
|
? 'partial'
|
|
184
242
|
: 'unavailable');
|
|
185
243
|
const http = HTTP_BINDINGS[capabilityKey];
|
|
186
|
-
const
|
|
244
|
+
const faceWasPassed = strippedFaces.length > 0;
|
|
187
245
|
const reason = hold && missing.length === 0
|
|
188
246
|
? why
|
|
189
|
-
: !ready &&
|
|
190
|
-
? `${why} — a next call for reaching a person names the user id or their agent, never a psn_ face`
|
|
247
|
+
: !ready && faceWasPassed
|
|
248
|
+
? `${why} — missing ${missing.join(', ')}. a next call for reaching a person names the user id or their agent, never a psn_ face`
|
|
191
249
|
: !ready
|
|
192
250
|
? `${why} — missing ${missing.join(', ')}`
|
|
193
251
|
: why;
|