@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.
- package/CHANGELOG.md +34 -0
- package/README.md +14 -36
- package/dist/capabilities/agreementVerbs.js +13 -21
- package/dist/capabilities/agreements.d.ts +2 -3
- package/dist/capabilities/agreements.js +8 -43
- package/dist/capabilities/artifacts.d.ts +6 -0
- package/dist/capabilities/artifacts.js +84 -43
- package/dist/capabilities/connections.js +10 -7
- package/dist/capabilities/context.d.ts +1 -1
- package/dist/capabilities/context.js +52 -45
- package/dist/capabilities/discovery.js +5 -5
- package/dist/capabilities/everyday.d.ts +1 -1
- package/dist/capabilities/everyday.js +1 -2
- package/dist/capabilities/index.d.ts +3 -4
- package/dist/capabilities/index.js +3 -4
- package/dist/capabilities/introductions.d.ts +1 -0
- package/dist/capabilities/introductions.js +64 -17
- package/dist/capabilities/links.d.ts +1 -9
- package/dist/capabilities/links.js +40 -39
- package/dist/capabilities/mcpBriefs.js +41 -34
- package/dist/capabilities/messaging.d.ts +9 -0
- package/dist/capabilities/messaging.js +11 -0
- package/dist/capabilities/nextCall.js +8 -17
- package/dist/capabilities/sources.d.ts +2 -3
- package/dist/capabilities/sources.js +1 -2
- package/dist/capabilities/tasks.d.ts +1 -0
- package/dist/capabilities/tasks.js +43 -2
- package/dist/capabilities/toolLane.js +2 -2
- package/dist/engagementGuide.d.ts +2 -2
- package/dist/engagementGuide.js +6 -6
- package/dist/handles.d.ts +1 -1
- package/dist/handles.js +2 -2
- package/dist/http/AgreementClient.d.ts +25 -63
- package/dist/http/AgreementClient.js +14 -65
- package/dist/http/ArtifactsClient.d.ts +31 -19
- package/dist/http/ArtifactsClient.js +43 -62
- package/dist/http/ConnectionsClient.d.ts +6 -7
- package/dist/http/ConnectionsClient.js +5 -3
- package/dist/http/ContextDiscoveryClient.d.ts +2 -2
- package/dist/http/ContextGrantsClient.d.ts +9 -15
- package/dist/http/ContextGrantsClient.js +3 -5
- package/dist/http/ContextOpenClient.d.ts +5 -6
- package/dist/http/ContextOpenClient.js +11 -4
- package/dist/http/ContextReadClient.d.ts +23 -27
- package/dist/http/ContextReadClient.js +24 -23
- package/dist/http/ExecutionContextClient.d.ts +5 -1
- package/dist/http/ExecutionContextClient.js +48 -1
- package/dist/http/IntroductionsClient.d.ts +33 -4
- package/dist/http/IntroductionsClient.js +6 -2
- package/dist/http/MessageRecordsClient.d.ts +36 -0
- package/dist/http/MessageRecordsClient.js +30 -0
- package/dist/http/SessionsClient.d.ts +5 -5
- package/dist/http/SessionsClient.js +6 -6
- package/dist/http/TaskClient.d.ts +35 -6
- package/dist/http/TaskClient.js +38 -36
- package/dist/http/agreementFlows.d.ts +2 -3
- package/dist/http/agreementFlows.js +5 -5
- package/dist/http/index.d.ts +3 -5
- package/dist/http/index.js +3 -4
- package/dist/http/operatorHeaders.d.ts +3 -0
- package/dist/http/operatorHeaders.js +14 -5
- package/dist/inboxAck.d.ts +2 -2
- package/dist/inboxAck.js +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/types.d.ts +25 -55
- package/package.json +2 -2
- package/dist/capabilities/chat.d.ts +0 -43
- package/dist/capabilities/chat.js +0 -196
- package/dist/capabilities/chatListFields.d.ts +0 -14
- package/dist/capabilities/chatListFields.js +0 -61
- package/dist/http/ChatClient.d.ts +0 -187
- package/dist/http/ChatClient.js +0 -219
- package/dist/http/MessagesClient.d.ts +0 -40
- 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
|
|
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,
|
|
167
|
+
async requestMcpConnection({ serverUrl, tools, reason, context, recipientId, onBehalfOfUserId, }) {
|
|
168
168
|
if (!serverUrl)
|
|
169
169
|
throw new Error("requestMcpConnection: serverUrl is required");
|
|
170
|
-
|
|
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: '
|
|
7
|
+
type: 'address' | 'agreement' | 'connection';
|
|
8
8
|
label: string;
|
|
9
9
|
scopeRef: {
|
|
10
|
-
kind: '
|
|
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
|
-
*
|
|
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
|
-
*
|
|
75
|
-
* never content. Org scope is capped; `
|
|
76
|
-
*
|
|
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' | '
|
|
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
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
|
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,
|
|
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", "
|
|
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…/
|
|
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: '
|
|
52
|
+
scopeKind: 'agreement' | 'task';
|
|
54
53
|
scopeId: string;
|
|
55
|
-
permission: 'read'
|
|
56
|
-
temporal: 'from-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 ["
|
|
1
|
+
export declare const CONTEXT_READ_TYPES: readonly ["artifacts", "agreements", "tasks"];
|
|
2
2
|
export type ContextReadType = (typeof CONTEXT_READ_TYPES)[number];
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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 ["
|
|
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,
|
|
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
|
-
/** `
|
|
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 `
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
*
|
|
105
|
-
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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,
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
agreements: ['chat', 'agreement'],
|
|
28
|
+
artifacts: ['agreement', 'task', 'artifact'],
|
|
29
|
+
agreements: ['agreement'],
|
|
33
30
|
tasks: ['agreement', 'task'],
|
|
34
31
|
};
|
|
35
|
-
/** `
|
|
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 `
|
|
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,
|
|
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
|
-
*
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
throw new Error('ContextReadClient.snapshot:
|
|
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',
|
|
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' | '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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';
|