@ziggs-ai/api-client 0.38.2 → 0.39.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +14 -36
  3. package/dist/capabilities/agreementVerbs.js +13 -21
  4. package/dist/capabilities/agreements.d.ts +2 -3
  5. package/dist/capabilities/agreements.js +7 -42
  6. package/dist/capabilities/artifacts.js +41 -43
  7. package/dist/capabilities/connections.js +10 -7
  8. package/dist/capabilities/context.d.ts +1 -1
  9. package/dist/capabilities/context.js +41 -45
  10. package/dist/capabilities/discovery.js +5 -5
  11. package/dist/capabilities/everyday.d.ts +1 -1
  12. package/dist/capabilities/everyday.js +1 -2
  13. package/dist/capabilities/index.d.ts +1 -2
  14. package/dist/capabilities/index.js +1 -2
  15. package/dist/capabilities/introductions.d.ts +1 -1
  16. package/dist/capabilities/introductions.js +3 -11
  17. package/dist/capabilities/links.d.ts +1 -9
  18. package/dist/capabilities/links.js +7 -28
  19. package/dist/capabilities/mcpBriefs.js +22 -28
  20. package/dist/capabilities/messaging.d.ts +9 -0
  21. package/dist/capabilities/messaging.js +11 -0
  22. package/dist/capabilities/nextCall.js +8 -17
  23. package/dist/capabilities/sources.d.ts +2 -3
  24. package/dist/capabilities/sources.js +1 -2
  25. package/dist/capabilities/tasks.js +1 -1
  26. package/dist/capabilities/toolLane.js +2 -2
  27. package/dist/engagementGuide.d.ts +2 -2
  28. package/dist/engagementGuide.js +6 -6
  29. package/dist/handles.d.ts +1 -1
  30. package/dist/handles.js +2 -2
  31. package/dist/http/AgreementClient.d.ts +17 -64
  32. package/dist/http/AgreementClient.js +14 -65
  33. package/dist/http/ArtifactsClient.d.ts +17 -19
  34. package/dist/http/ArtifactsClient.js +30 -62
  35. package/dist/http/ConnectionsClient.d.ts +6 -7
  36. package/dist/http/ConnectionsClient.js +5 -3
  37. package/dist/http/ContextDiscoveryClient.d.ts +2 -2
  38. package/dist/http/ContextGrantsClient.d.ts +8 -15
  39. package/dist/http/ContextGrantsClient.js +3 -5
  40. package/dist/http/ContextOpenClient.d.ts +5 -6
  41. package/dist/http/ContextOpenClient.js +11 -4
  42. package/dist/http/ContextReadClient.d.ts +23 -27
  43. package/dist/http/ContextReadClient.js +24 -23
  44. package/dist/http/ExecutionContextClient.d.ts +5 -1
  45. package/dist/http/ExecutionContextClient.js +48 -1
  46. package/dist/http/IntroductionsClient.d.ts +6 -4
  47. package/dist/http/MessageRecordsClient.d.ts +36 -0
  48. package/dist/http/MessageRecordsClient.js +30 -0
  49. package/dist/http/SessionsClient.d.ts +5 -5
  50. package/dist/http/SessionsClient.js +6 -6
  51. package/dist/http/TaskClient.d.ts +11 -5
  52. package/dist/http/TaskClient.js +11 -34
  53. package/dist/http/agreementFlows.d.ts +2 -3
  54. package/dist/http/agreementFlows.js +5 -5
  55. package/dist/http/index.d.ts +3 -5
  56. package/dist/http/index.js +3 -4
  57. package/dist/http/operatorHeaders.d.ts +3 -0
  58. package/dist/http/operatorHeaders.js +14 -5
  59. package/dist/inboxAck.d.ts +2 -2
  60. package/dist/inboxAck.js +2 -2
  61. package/dist/index.d.ts +4 -4
  62. package/dist/index.js +2 -2
  63. package/dist/types.d.ts +15 -55
  64. package/package.json +2 -2
  65. package/dist/capabilities/chat.d.ts +0 -43
  66. package/dist/capabilities/chat.js +0 -196
  67. package/dist/capabilities/chatListFields.d.ts +0 -14
  68. package/dist/capabilities/chatListFields.js +0 -61
  69. package/dist/http/ChatClient.d.ts +0 -187
  70. package/dist/http/ChatClient.js +0 -219
  71. package/dist/http/MessagesClient.d.ts +0 -40
  72. package/dist/http/MessagesClient.js +0 -81
@@ -4,7 +4,6 @@ import { pollSurfaceError } from '../shared/rateLimit.js';
4
4
  import { buildOperatorHeaders } from './operatorHeaders.js';
5
5
  export const CONTEXT_OPEN_KINDS = [
6
6
  'artifact',
7
- 'chat',
8
7
  'task',
9
8
  'agreement',
10
9
  ];
@@ -43,21 +42,29 @@ export class ContextOpenClient {
43
42
  if (!res.ok) {
44
43
  throw pollSurfaceError(`ContextOpenClient ${path}`, res, text);
45
44
  }
46
- return JSON.parse(text);
45
+ const result = JSON.parse(text);
46
+ if (result?.ref?.kind === 'address') {
47
+ throw Object.assign(new Error('Room references are retired; select an exact artifact, task or agreement'), { code: 'INVALID_INPUT' });
48
+ }
49
+ return result;
47
50
  }
48
51
  }
49
52
  export function normalizeContextOpenBody(args) {
50
53
  // A mistyped handle is refused here, before any call: its check letter
51
54
  // fails. Any other id goes to the server, which knows older shapes.
52
55
  const invalid = (message) => Object.assign(new Error(message), { code: 'INVALID_INPUT' });
53
- const keys = ['ref', 'artifactId', 'chatId', 'taskId', 'agreementId'].filter(key => args[key] !== undefined);
56
+ if ('addressId' in args)
57
+ throw invalid('addressId is retired; select an exact artifact, task or agreement');
58
+ const keys = ['ref', 'artifactId', 'taskId', 'agreementId'].filter(key => args[key] !== undefined);
54
59
  if (keys.length !== 1)
55
- throw invalid('Pass exactly one resource reference: ref, artifactId, chatId, taskId, or agreementId');
60
+ throw invalid('Pass exactly one resource reference: ref, artifactId, taskId, or agreementId');
56
61
  const key = keys[0];
57
62
  const raw = typeof args[key] === 'string' ? args[key].trim() : '';
58
63
  if (!raw)
59
64
  throw invalid(`${key} must be a non-empty resource reference`);
60
65
  const ref = parseRef(raw);
66
+ if (ref?.kind === 'address' || /^(?:address|chat):/.test(raw))
67
+ throw invalid('Room references are retired; select an exact artifact, task or agreement');
61
68
  if (!ref && looksLikeHandle(raw))
62
69
  throw invalid(`${raw} is not a handle: its check letter does not match. Check it for a typo`);
63
70
  const body = { [key]: ref?.id ?? raw };
@@ -1,23 +1,23 @@
1
- export declare const CONTEXT_READ_TYPES: readonly ["messages", "artifacts", "agreements", "tasks"];
1
+ export declare const CONTEXT_READ_TYPES: readonly ["artifacts", "agreements", "tasks"];
2
2
  export type ContextReadType = (typeof CONTEXT_READ_TYPES)[number];
3
3
  /**
4
- * Entry points a read can name as `via=<kind>:<id>` — the server's `parseVia`
5
- * grammar. `counterparty` parses but reads nothing: it resolves a scope graph,
6
- * not content, which is why {@link CONTEXT_READ_VIA} admits it for no type.
4
+ * Retained resource entry points for context reads and snapshots.
5
+ * The server still accepts legacy room entries until coordinated removal;
6
+ * new callers use only the domain resources below.
7
7
  */
8
- export declare const VIA_KINDS: readonly ["chat", "agreement", "task", "counterparty", "artifact"];
8
+ export declare const VIA_KINDS: readonly ["agreement", "task", "artifact", "message"];
9
9
  export type ViaKind = (typeof VIA_KINDS)[number];
10
10
  /**
11
- * Which entry points each read type actually accepts, mirroring the server's
11
+ * Which entry points each read type actually accepts, using the retained subset of the server's
12
12
  * `CONTEXT_READ_VIA`. Stated once here so the tool descriptions, the argument
13
13
  * check, and this type all read off the same list instead of three prose
14
14
  * copies that drift — and so a wrong pairing is refused with the reason rather
15
15
  * than as a bare 400 from a round-trip away.
16
16
  */
17
17
  export declare const CONTEXT_READ_VIA: Record<ContextReadType, readonly ViaKind[]>;
18
- /** `chat:<id>, agreement:<id>` — the accepted entries for one read type, for humans. */
18
+ /** `agreement:<id>, task:<id>` — the accepted entries for one read type, for humans. */
19
19
  export declare function viaHint(type: ContextReadType): string;
20
- /** Split `chat:abc` into its parts, or null when it is not a `via` at all. */
20
+ /** Split `agreement:abc` into its parts, or null when it is not a `via` at all. */
21
21
  export declare function parseVia(via: string): {
22
22
  kind: ViaKind;
23
23
  id: string;
@@ -43,7 +43,7 @@ export interface ContextReadEnvelope<T = unknown> {
43
43
  nextCursor: string | null;
44
44
  latestSequence?: string | null;
45
45
  }
46
- /** Aggregated chat snapshot from `GET /context/snapshot` (follow-up). */
46
+ /** Authorized resource snapshot from `GET /context/snapshot`. */
47
47
  /**
48
48
  * One participant on the snapshot roster. `kind` is the declared holder kind the
49
49
  * server stamped on the row, so a reader takes what a participant IS from the
@@ -56,6 +56,14 @@ export interface ContextSnapshotParticipant {
56
56
  isYou?: boolean;
57
57
  [key: string]: unknown;
58
58
  }
59
+ export interface ContextSnapshotQuery {
60
+ /** Exact message, task, agreement, or artifact entry; no implicit room. */
61
+ via: string;
62
+ agreementId?: string;
63
+ artifactId?: string;
64
+ maxMessages?: number;
65
+ contextGrantId?: string;
66
+ }
59
67
  export interface ContextSnapshotResult {
60
68
  /** Server-resolved orientation; writes recheck authority. */
61
69
  workBrief?: Record<string, unknown> & {
@@ -64,14 +72,14 @@ export interface ContextSnapshotResult {
64
72
  work?: Record<string, unknown>;
65
73
  history: unknown[];
66
74
  agreements: unknown[];
67
- /** Bounded room inventory, without bodies. Absent on older backends. */
75
+ /** Bounded authorized artifact inventory, without bodies. */
68
76
  artifacts?: ContextReadEnvelope<{
69
77
  artifactId: string;
70
78
  name: string | null;
71
79
  contentType: string;
72
80
  timestamp: string | null;
73
81
  }>;
74
- /** Direct artifact grants missing from the room inventory. Id and name only. */
82
+ /** Direct artifact grants outside the selected work inventory. Id and name only. */
75
83
  grantedArtifacts?: {
76
84
  items: Array<{
77
85
  artifactId: string;
@@ -87,11 +95,10 @@ export interface ContextSnapshotResult {
87
95
  userId: string;
88
96
  }>;
89
97
  latestSequence?: string | null;
90
- chatMissing?: boolean;
91
98
  }
92
99
  /**
93
100
  * Protocol-first uniform context reads.
94
- * Wraps `GET /context/read/:type` — one client, one envelope, four types.
101
+ * Wraps `GET /context/read/:type` — one client, one envelope, three types.
95
102
  */
96
103
  export declare class ContextReadClient {
97
104
  private readonly operatorKey;
@@ -100,18 +107,7 @@ export declare class ContextReadClient {
100
107
  /** Authority is supplied by withWorkExecution / withWorkExecutionId. */
101
108
  constructor(operatorKey: string, agentId?: string, baseUrl?: string);
102
109
  read<T = unknown>(type: ContextReadType, query: ContextReadQuery): Promise<ContextReadEnvelope<T>>;
103
- /**
104
- * Aggregated chat snapshot — `GET /context/snapshot?via=chat:<id>`. The
105
- * server composes history + agreements + roster through the same grant-fenced
106
- * readers as {@link read}, so hosted agents pull one fenced view per poll cycle.
107
- * Rejects with an `Error & { status }` on non-OK so callers can branch (404).
108
- */
109
- snapshot(chatId: string, opts?: {
110
- maxMessages?: number;
111
- contextGrantId?: string;
112
- agreementId?: string;
113
- taskId?: string;
114
- artifactId?: string;
115
- via?: string;
116
- }): Promise<ContextSnapshotResult>;
110
+ /** A snapshot selects one domain resource; the current execution headers
111
+ * remain authoritative and are never cleared after a refusal. */
112
+ snapshot(opts: ContextSnapshotQuery): Promise<ContextSnapshotResult>;
117
113
  }
@@ -2,41 +2,38 @@ import { getBackendUrl } from '../utils/urlUtils.js';
2
2
  import { pollSurfaceError } from '../shared/rateLimit.js';
3
3
  import { buildOperatorHeaders } from './operatorHeaders.js';
4
4
  export const CONTEXT_READ_TYPES = [
5
- 'messages',
6
5
  'artifacts',
7
6
  'agreements',
8
7
  'tasks',
9
8
  ];
10
9
  /**
11
- * Entry points a read can name as `via=<kind>:<id>` — the server's `parseVia`
12
- * grammar. `counterparty` parses but reads nothing: it resolves a scope graph,
13
- * not content, which is why {@link CONTEXT_READ_VIA} admits it for no type.
10
+ * Retained resource entry points for context reads and snapshots.
11
+ * The server still accepts legacy room entries until coordinated removal;
12
+ * new callers use only the domain resources below.
14
13
  */
15
14
  export const VIA_KINDS = [
16
- 'chat',
17
15
  'agreement',
18
16
  'task',
19
- 'counterparty',
20
17
  'artifact',
18
+ 'message',
21
19
  ];
22
20
  /**
23
- * Which entry points each read type actually accepts, mirroring the server's
21
+ * Which entry points each read type actually accepts, using the retained subset of the server's
24
22
  * `CONTEXT_READ_VIA`. Stated once here so the tool descriptions, the argument
25
23
  * check, and this type all read off the same list instead of three prose
26
24
  * copies that drift — and so a wrong pairing is refused with the reason rather
27
25
  * than as a bare 400 from a round-trip away.
28
26
  */
29
27
  export const CONTEXT_READ_VIA = {
30
- messages: ['chat'],
31
- artifacts: ['chat', 'agreement', 'task', 'artifact'],
32
- agreements: ['chat', 'agreement'],
28
+ artifacts: ['agreement', 'task', 'artifact'],
29
+ agreements: ['agreement'],
33
30
  tasks: ['agreement', 'task'],
34
31
  };
35
- /** `chat:<id>, agreement:<id>` — the accepted entries for one read type, for humans. */
32
+ /** `agreement:<id>, task:<id>` — the accepted entries for one read type, for humans. */
36
33
  export function viaHint(type) {
37
34
  return CONTEXT_READ_VIA[type].map((k) => `${k}:<id>`).join(', ');
38
35
  }
39
- /** Split `chat:abc` into its parts, or null when it is not a `via` at all. */
36
+ /** Split `agreement:abc` into its parts, or null when it is not a `via` at all. */
40
37
  export function parseVia(via) {
41
38
  const at = via.indexOf(':');
42
39
  if (at <= 0)
@@ -51,7 +48,7 @@ export function parseVia(via) {
51
48
  }
52
49
  /**
53
50
  * Protocol-first uniform context reads.
54
- * Wraps `GET /context/read/:type` — one client, one envelope, four types.
51
+ * Wraps `GET /context/read/:type` — one client, one envelope, three types.
55
52
  */
56
53
  export class ContextReadClient {
57
54
  operatorKey;
@@ -69,6 +66,10 @@ export class ContextReadClient {
69
66
  if (!query.via?.trim()) {
70
67
  throw new Error('ContextReadClient.read: via is required');
71
68
  }
69
+ const parsed = parseVia(query.via.trim());
70
+ if (!parsed || !CONTEXT_READ_VIA[type]?.includes(parsed.kind)) {
71
+ throw new Error(`ContextReadClient.read: ${type} reads accept via=${CONTEXT_READ_VIA[type] ? viaHint(type) : 'a retained resource'}`);
72
+ }
72
73
  const url = new URL(`${this.baseUrl}/context/read/${encodeURIComponent(type)}`);
73
74
  url.searchParams.set('via', query.via.trim());
74
75
  if (query.agreementId)
@@ -101,18 +102,18 @@ export class ContextReadClient {
101
102
  }
102
103
  return JSON.parse(body);
103
104
  }
104
- /**
105
- * Aggregated chat snapshot — `GET /context/snapshot?via=chat:<id>`. The
106
- * server composes history + agreements + roster through the same grant-fenced
107
- * readers as {@link read}, so hosted agents pull one fenced view per poll cycle.
108
- * Rejects with an `Error & { status }` on non-OK so callers can branch (404).
109
- */
110
- async snapshot(chatId, opts = {}) {
111
- if (!chatId?.trim()) {
112
- throw new Error('ContextReadClient.snapshot: chatId is required');
105
+ /** A snapshot selects one domain resource; the current execution headers
106
+ * remain authoritative and are never cleared after a refusal. */
107
+ async snapshot(opts) {
108
+ const via = opts?.via?.trim();
109
+ if (!via || !parseVia(via)) {
110
+ throw new Error('ContextReadClient.snapshot: an explicit resource is required (message:<id>, task:<id>, agreement:<id>, or artifact:<id>)');
111
+ }
112
+ if (parseVia(via)?.kind === 'message' && (opts.agreementId || opts.artifactId)) {
113
+ throw new Error('ContextReadClient.snapshot: select an exact message independently of other resources');
113
114
  }
114
115
  const url = new URL(`${this.baseUrl}/context/snapshot`);
115
- url.searchParams.set('via', opts.taskId ? `task:${opts.taskId}` : opts.via ?? (chatId.startsWith('agrn-') ? `agreement:${opts.agreementId ?? chatId.slice(5)}` : `chat:${chatId}`));
116
+ url.searchParams.set('via', via);
116
117
  if (opts.agreementId)
117
118
  url.searchParams.set('agreementId', opts.agreementId);
118
119
  if (opts.artifactId)
@@ -1,3 +1,7 @@
1
- import type { WorkExecutionContext } from '@ziggs-ai/contracts';
1
+ import type { ActionRef, WorkExecutionContext } from '@ziggs-ai/contracts';
2
2
  /** Fail closed before resuming model state, including timers and recorded wakes. */
3
3
  export declare function validateWorkExecution(context: WorkExecutionContext, operatorKey: string, baseUrl?: string): Promise<void>;
4
+ /** Explicit admission, outside an existing execution. Never retries a refused context. */
5
+ export declare function prepareWorkExecution(action: ActionRef, operatorKey: string, agentId: string, baseUrl?: string): Promise<WorkExecutionContext>;
6
+ /** The host holds a fenced lease while it runs; release is not domain completion. */
7
+ export declare function runWorkExecution<T>(context: WorkExecutionContext, operatorKey: string, run: (context: WorkExecutionContext) => Promise<T>, baseUrl?: string): Promise<T>;
@@ -1,5 +1,5 @@
1
1
  import { getBackendUrl } from '../utils/urlUtils.js';
2
- import { buildOperatorHeaders, withWorkExecution } from './operatorHeaders.js';
2
+ import { buildOperatorHeaders, withWorkExecution, withoutExecution } from './operatorHeaders.js';
3
3
  import { throwApiError } from '../shared/apiError.js';
4
4
  /** Fail closed before resuming model state, including timers and recorded wakes. */
5
5
  export async function validateWorkExecution(context, operatorKey, baseUrl = getBackendUrl()) {
@@ -15,3 +15,50 @@ export async function validateWorkExecution(context, operatorKey, baseUrl = getB
15
15
  throw new Error('Execution preflight returned a different context');
16
16
  });
17
17
  }
18
+ /** Explicit admission, outside an existing execution. Never retries a refused context. */
19
+ export async function prepareWorkExecution(action, operatorKey, agentId, baseUrl = getBackendUrl()) {
20
+ const response = await fetch(`${baseUrl}/execution/prepare`, {
21
+ method: 'POST', headers: { ...buildOperatorHeaders(operatorKey, agentId), 'content-type': 'application/json' },
22
+ body: JSON.stringify(action),
23
+ });
24
+ const text = await response.text();
25
+ if (!response.ok)
26
+ throwApiError(response, text, 'Action execution is unavailable');
27
+ const context = JSON.parse(text);
28
+ if (!context.contextId || context.actorId !== agentId)
29
+ throw new Error('Invalid action execution response');
30
+ return context;
31
+ }
32
+ /** The host holds a fenced lease while it runs; release is not domain completion. */
33
+ export async function runWorkExecution(context, operatorKey, run, baseUrl = getBackendUrl()) {
34
+ const call = async (body, selected = context) => withWorkExecution(selected, async () => {
35
+ const finish = body.operation === 'finish';
36
+ const headers = buildOperatorHeaders(operatorKey, context.actorId);
37
+ const response = await fetch(`${baseUrl}/execution/attempt`, { method: 'POST',
38
+ headers: { ...(finish ? withoutExecution(headers) : headers), 'content-type': 'application/json' },
39
+ body: JSON.stringify(finish ? { ...body, contextId: selected.contextId, attemptId: selected.attemptId } : body) });
40
+ const text = await response.text();
41
+ if (!response.ok)
42
+ throwApiError(response, text, 'Execution attempt refused');
43
+ return JSON.parse(text);
44
+ });
45
+ const attempt = await call({ operation: 'claim' });
46
+ if (attempt.contextId !== context.contextId || !attempt.attemptId)
47
+ throw new Error('Invalid execution attempt response');
48
+ let lost;
49
+ const timer = setInterval(() => { void call({ operation: 'renew' }, attempt).catch(error => { lost = error; }); }, 20_000);
50
+ timer.unref();
51
+ try {
52
+ const result = await withWorkExecution(attempt, () => run(attempt));
53
+ if (lost)
54
+ throw lost;
55
+ return result;
56
+ }
57
+ finally {
58
+ clearInterval(timer);
59
+ // A stale worker never releases its replacement. Failure here keeps durable
60
+ // inputs pending; it must not transform an execution refusal into a retry
61
+ // without its selected authority.
62
+ await call({ operation: 'finish' }, attempt);
63
+ }
64
+ }
@@ -6,8 +6,10 @@ export type IntroductionStatus = 'open' | 'redeemed' | 'declined' | 'revoked' |
6
6
  * (your own token, a teammate's, or someone you are already linked to).
7
7
  * `claimed` / `bid` / `accepted`: the listing, request or proposal it names.
8
8
  * `link_pending`: a link both people still approve.
9
+ * `held`: an agent with no person around holds it as a guest until a person
10
+ * claims that agent; the claim finishes the redeem as them.
9
11
  */
10
- export type IntroductionOutcome = 'joined' | 'claimed' | 'bid' | 'accepted' | 'link_pending';
12
+ export type IntroductionOutcome = 'joined' | 'claimed' | 'bid' | 'accepted' | 'link_pending' | 'held';
11
13
  export interface IntroductionFrom {
12
14
  orgId: string;
13
15
  agentId: string | null;
@@ -34,7 +36,7 @@ export interface IntroductionView {
34
36
  note: string | null;
35
37
  /** What the token is about. The public view gives kind and title only. */
36
38
  about?: {
37
- kind: 'agreement' | 'task' | 'chat' | 'workspace';
39
+ kind: 'agreement' | 'task' | 'address' | 'workspace';
38
40
  id?: string;
39
41
  title: string | null;
40
42
  shape: 'listing' | 'request' | 'proposal' | null;
@@ -60,7 +62,7 @@ export interface IntroductionView {
60
62
  at: string;
61
63
  }>;
62
64
  /** Returned to a redeemer only: the room its agent is now in. */
63
- chatId?: string | null;
65
+ addressId?: string | null;
64
66
  expiresAt: string;
65
67
  /** The public page the string points at. This is what you hand over. */
66
68
  url: string;
@@ -89,7 +91,7 @@ export interface MintIntroductionInput {
89
91
  note?: string;
90
92
  /** What the token is about: a listing, request or proposal (agreement), a task, or a chat you hold. */
91
93
  about?: {
92
- kind: 'agreement' | 'task' | 'chat' | 'workspace';
94
+ kind: 'agreement' | 'task' | 'address' | 'workspace';
93
95
  id: string;
94
96
  };
95
97
  /** How many people it serves, 1 to 25. Your own sessions never use one up. */
@@ -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';
@@ -0,0 +1,30 @@
1
+ import { parseMessageCommand } from '@ziggs-ai/contracts';
2
+ import { buildOperatorHeaders } from './operatorHeaders.js';
3
+ import { getBackendUrl } from '../utils/urlUtils.js';
4
+ import { throwApiError } from '../shared/apiError.js';
5
+ /** One command and one representation, independent of navigation. */
6
+ export class MessageRecordsClient {
7
+ operatorKey;
8
+ agentId;
9
+ baseUrl;
10
+ constructor(operatorKey, agentId, baseUrl) {
11
+ this.operatorKey = operatorKey;
12
+ this.agentId = agentId;
13
+ this.baseUrl = baseUrl;
14
+ }
15
+ async request(path, init = {}) { const res = await fetch(`${this.baseUrl ?? getBackendUrl()}/messaging/${path}`, { ...init, headers: { 'content-type': 'application/json', ...buildOperatorHeaders(this.operatorKey, this.agentId) } }); if (!res.ok)
16
+ throwApiError(res, await res.text().catch(() => ''), `Messaging failed: ${res.status}`); return await res.json(); }
17
+ async get(id) { const row = await this.request(`messages/${encodeURIComponent(id)}`); if (row.messageId !== id || !row.content || !Array.isArray(row.requestedAnswers))
18
+ throw new Error('Invalid exact message response'); return row; }
19
+ async send(command) { const result = await this.request('messages', { method: 'POST', body: JSON.stringify(parseMessageCommand(command)) }); if (!['created', 'existing'].includes(result.outcome) || !result.message?.messageId || !result.message.content || !Array.isArray(result.message.recipients) || !Array.isArray(result.message.requestedAnswers))
20
+ throw new Error('Invalid message command response'); return result; }
21
+ list(input = {}) { const query = new URLSearchParams(); for (const [key, value] of Object.entries(input))
22
+ if (value !== undefined)
23
+ query.set(key, Array.isArray(value) ? value.join(',') : String(value)); return this.request(`messages?${query}`); }
24
+ replies(id, input = {}) { const query = new URLSearchParams(); for (const [key, value] of Object.entries(input))
25
+ if (value !== undefined)
26
+ query.set(key, String(value)); return this.request(`messages/${encodeURIComponent(id)}/replies?${query}`); }
27
+ controls(agreementId, taskId) { const query = new URLSearchParams({ agreementId, ...(taskId ? { taskId } : {}) }); return this.request(`controls?${query}`); }
28
+ answers(id) { return this.request(`messages/${encodeURIComponent(id)}/answers`); }
29
+ cancelAnswer(id, requestId) { return this.request(`messages/${encodeURIComponent(id)}/answers/${encodeURIComponent(requestId)}/cancel`, { method: 'POST' }); }
30
+ }
@@ -8,11 +8,11 @@ export interface AssistantSessionRow {
8
8
  lastSeenAt: string;
9
9
  expiresAt: string;
10
10
  workspaceIds: string[];
11
- heldChatIds: string[];
11
+ heldExecutionIds: string[];
12
12
  awake: boolean;
13
13
  }
14
14
  /**
15
- * List sibling windows and hold a chat in this one.
15
+ * List sibling windows and hold an execution in this one.
16
16
  * Uses the same session headers as the inbox so "this window" is the
17
17
  * X-Ziggs-Session the host already sends.
18
18
  */
@@ -23,12 +23,12 @@ export declare class SessionsClient {
23
23
  private url;
24
24
  list(): Promise<AssistantSessionRow[]>;
25
25
  /**
26
- * Hold `chatId` in this window (omit sessionId; server resolves from
26
+ * Hold `executionContextId` in this window (omit sessionId; server resolves from
27
27
  * X-Ziggs-Session), move it to `sessionId`, or pass null to release.
28
28
  */
29
- hold(chatId: string, sessionId?: string | null): Promise<{
29
+ hold(executionContextId: string, sessionId?: string | null): Promise<{
30
30
  success: true;
31
- chatId: string;
31
+ executionContextId: string;
32
32
  sessionId: string | null;
33
33
  }>;
34
34
  }
@@ -2,7 +2,7 @@ import { getBackendUrl } from '../utils/urlUtils.js';
2
2
  import { pollSurfaceError } from '../shared/rateLimit.js';
3
3
  import { buildCredsHeaders } from './operatorHeaders.js';
4
4
  /**
5
- * List sibling windows and hold a chat in this one.
5
+ * List sibling windows and hold an execution in this one.
6
6
  * Uses the same session headers as the inbox so "this window" is the
7
7
  * X-Ziggs-Session the host already sends.
8
8
  */
@@ -48,14 +48,14 @@ export class SessionsClient {
48
48
  return Array.isArray(rows) ? rows : [];
49
49
  }
50
50
  /**
51
- * Hold `chatId` in this window (omit sessionId; server resolves from
51
+ * Hold `executionContextId` in this window (omit sessionId; server resolves from
52
52
  * X-Ziggs-Session), move it to `sessionId`, or pass null to release.
53
53
  */
54
- async hold(chatId, sessionId) {
55
- if (!chatId?.trim())
56
- throw new Error('SessionsClient.hold: chatId is required');
54
+ async hold(executionContextId, sessionId) {
55
+ if (!executionContextId?.trim())
56
+ throw new Error('SessionsClient.hold: executionContextId is required');
57
57
  const body = {
58
- chatId: chatId.trim(),
58
+ executionContextId: executionContextId.trim(),
59
59
  };
60
60
  if (sessionId !== undefined)
61
61
  body.sessionId = sessionId;
@@ -1,3 +1,5 @@
1
+ import { type TaskUpdateState } from '@ziggs-ai/contracts';
2
+ export type { TaskUpdateState } from '@ziggs-ai/contracts';
1
3
  import type { WriteSource } from '../capabilities/sources.js';
2
4
  import { type Creds, type Task, type TaskState } from '../types.js';
3
5
  /**
@@ -5,6 +7,8 @@ import { type Creds, type Task, type TaskState } from '../types.js';
5
7
  * getTask / ziggs_task_get.
6
8
  */
7
9
  export interface TaskWriteConfirm {
10
+ success: true;
11
+ /** @deprecated Use success. */
8
12
  ok: true;
9
13
  taskId: string;
10
14
  /** The agreement the task hangs under, when the server reports it. */
@@ -152,9 +156,8 @@ export interface UpdateTaskStateData {
152
156
  /** What the result drew on. Recorded only when a result is written. */
153
157
  sources?: WriteSource[];
154
158
  }
155
- export declare function updateTaskState(taskId: string, state: TaskState, data: UpdateTaskStateData | undefined, creds: Creds): Promise<TaskWriteConfirm>;
159
+ export declare function updateTaskState(taskId: string, state: TaskUpdateState, data: UpdateTaskStateData | undefined, creds: Creds): Promise<TaskWriteConfirm>;
156
160
  export declare function getActiveTasksForAgent(agentId: string, creds: Creds): Promise<Task[]>;
157
- export declare function getActiveTasksForChat(chatId: string, creds: Creds): Promise<Task[]>;
158
161
  export declare function cancelTask(taskId: string, creds: Creds): Promise<TaskWriteConfirm>;
159
162
  export declare function getSubtasks(parentTaskId: string, creds: Creds): Promise<Task[]>;
160
163
  export interface PlanReplaceStep {
@@ -205,6 +208,8 @@ export interface ListTasksOptions {
205
208
  limit?: number;
206
209
  /** Filter to tasks assigned to this id. */
207
210
  assignedTo?: string;
211
+ /** Stored assignee, including tasks held by dependencies. */
212
+ assigneeId?: string;
208
213
  /**
209
214
  * Filter to tasks CREATED by this id — what the caller handed out, as
210
215
  * opposed to what was handed to it.
@@ -213,12 +218,14 @@ export interface ListTasksOptions {
213
218
  }
214
219
  export interface ListTasksResult {
215
220
  tasks: Task[];
221
+ /** Number of tasks on this page, not a total across pages. */
222
+ count: number;
216
223
  hasMore: boolean;
217
224
  nextCursor?: string | null;
218
225
  }
219
226
  export declare function listTasks(options: ListTasksOptions | undefined, creds: Creds): Promise<ListTasksResult>;
220
227
  export interface CountTasksFilters {
221
- chatId?: string;
228
+ agreementId?: string;
222
229
  state?: string;
223
230
  userId?: string;
224
231
  agentId?: string;
@@ -237,9 +244,8 @@ export declare class TaskClient {
237
244
  children?: CreatedTaskChild[];
238
245
  }>;
239
246
  getTask(taskId: string): Promise<Task>;
240
- updateTaskState(taskId: string, state: TaskState, data?: UpdateTaskStateData): Promise<TaskWriteConfirm>;
247
+ updateTaskState(taskId: string, state: TaskUpdateState, data?: UpdateTaskStateData): Promise<TaskWriteConfirm>;
241
248
  getActiveTasksForAgent(agentId: string): Promise<Task[]>;
242
- getActiveTasksForChat(chatId: string): Promise<Task[]>;
243
249
  cancelTask(taskId: string): Promise<TaskWriteConfirm>;
244
250
  getSubtasks(parentTaskId: string): Promise<Task[]>;
245
251
  replaceTaskPlan(taskId: string, steps: PlanReplaceStep[]): Promise<TaskWriteConfirm>;
@@ -1,3 +1,4 @@
1
+ import { TASK_UPDATE_STATES } from '@ziggs-ai/contracts';
1
2
  import { decodeOperatorKeyClaims } from '../shared/operatorKey.js';
2
3
  import { getBackendUrl } from '../utils/urlUtils.js';
3
4
  import { partyActorIds } from '../types.js';
@@ -59,6 +60,7 @@ function extractWriteConfirm(data) {
59
60
  d['task'] === undefined &&
60
61
  d['description'] === undefined) {
61
62
  return {
63
+ success: true,
62
64
  ok: true,
63
65
  ...(d['control'] ? { control: d['control'] } : {}),
64
66
  taskId: d['taskId'],
@@ -86,6 +88,7 @@ function extractWriteConfirm(data) {
86
88
  if (!task?.taskId || !task.state)
87
89
  return null;
88
90
  return {
91
+ success: true,
89
92
  ok: true,
90
93
  taskId: task.taskId,
91
94
  state: task.state,
@@ -142,6 +145,9 @@ export async function updateTaskState(taskId, state, data = {}, creds) {
142
145
  throw new Error('updateTaskState: taskId is required');
143
146
  if (!state)
144
147
  throw new Error('updateTaskState: state is required');
148
+ if (!TASK_UPDATE_STATES.includes(state)) {
149
+ throw new Error('updateTaskState accepts active, completed or failed; use cancelTask to cancel');
150
+ }
145
151
  assertCreds(creds, 'task state update');
146
152
  const res = await fetch(`${getTaskBaseUrl()}/${taskId}/state`, {
147
153
  method: 'PATCH',
@@ -219,37 +225,6 @@ async function getActiveTasksForAgentViaPartyAgreements(agentId, creds) {
219
225
  const merged = lists.flat();
220
226
  return workable(merged.filter((t) => !t.assigneeId || t.assigneeId === agentId));
221
227
  }
222
- // Backend deliberately has no `/chats/:id/tasks` route — it's composable
223
- // from links → per-agreement tasks. We do the composition here so callers
224
- // don't have to.
225
- export async function getActiveTasksForChat(chatId, creds) {
226
- if (!chatId)
227
- throw new Error('getActiveTasksForChat: chatId is required');
228
- assertCreds(creds, 'getting active tasks for chat');
229
- const linksRes = await fetch(`${getBackendUrl()}/agreements/by-chat/${encodeURIComponent(chatId)}`, { method: 'GET', headers: buildHeaders(creds) });
230
- if (!linksRes.ok) {
231
- const body = await linksRes.text().catch(() => '');
232
- throwApiError(linksRes, body, `Get agreements for chat failed: ${linksRes.status} ${linksRes.statusText}`);
233
- }
234
- const linksData = await linksRes.json().catch(() => null);
235
- const rawLinks = linksData?.['links'] ?? linksData?.['agreements'];
236
- const agreements = Array.isArray(rawLinks) ? rawLinks : [];
237
- const agreementIds = Array.from(new Set(agreements.map((a) => a['agreementId']).filter(Boolean)));
238
- if (agreementIds.length === 0)
239
- return [];
240
- const lists = await Promise.all(agreementIds.map(async (aid) => {
241
- const url = new URL(`${getBackendUrl()}/agreements/${encodeURIComponent(aid)}/tasks`);
242
- url.searchParams.set('state', 'active');
243
- const res = await fetch(url.toString(), { method: 'GET', headers: buildHeaders(creds) });
244
- if (!res.ok) {
245
- const body = await res.text().catch(() => '');
246
- throwApiError(res, body, `Get tasks for agreement ${aid} failed: ${res.status}`);
247
- }
248
- const data = await res.json().catch(() => null);
249
- return Array.isArray(data?.['tasks']) ? data['tasks'] : [];
250
- }));
251
- return lists.flat();
252
- }
253
228
  export async function cancelTask(taskId, creds) {
254
229
  if (!taskId)
255
230
  throw new Error('cancelTask: taskId is required');
@@ -426,6 +401,8 @@ export async function listTasks(options = {}, creds) {
426
401
  url.searchParams.set('limit', String(options.limit));
427
402
  if (options.assignedTo)
428
403
  url.searchParams.set('assignedTo', options.assignedTo);
404
+ if (options.assigneeId)
405
+ url.searchParams.set('assigneeId', options.assigneeId);
429
406
  if (options.createdBy)
430
407
  url.searchParams.set('createdBy', options.createdBy);
431
408
  const res = await fetch(url.toString(), {
@@ -440,6 +417,7 @@ export async function listTasks(options = {}, creds) {
440
417
  const tasks = Array.isArray(data?.['tasks']) ? data['tasks'] : [];
441
418
  return {
442
419
  tasks,
420
+ count: tasks.length,
443
421
  hasMore: Boolean(data?.['hasMore']),
444
422
  nextCursor: data?.['nextCursor'] ?? null,
445
423
  };
@@ -448,8 +426,8 @@ export async function countTasks(filters = {}, creds) {
448
426
  assertCreds(creds, 'count tasks');
449
427
  // Canonical REST: GET /tasks/count.
450
428
  const url = new URL(`${getTaskBaseUrl()}/count`);
451
- if (filters.chatId)
452
- url.searchParams.set('chatId', filters.chatId);
429
+ if (filters.agreementId)
430
+ url.searchParams.set('agreementId', filters.agreementId);
453
431
  if (filters.state)
454
432
  url.searchParams.set('state', filters.state);
455
433
  if (filters.userId)
@@ -489,7 +467,6 @@ export class TaskClient {
489
467
  getTask(taskId) { return getTask(taskId, this.creds); }
490
468
  updateTaskState(taskId, state, data) { return updateTaskState(taskId, state, data ?? {}, this.creds); }
491
469
  getActiveTasksForAgent(agentId) { return getActiveTasksForAgent(agentId, this.creds); }
492
- getActiveTasksForChat(chatId) { return getActiveTasksForChat(chatId, this.creds); }
493
470
  cancelTask(taskId) { return cancelTask(taskId, this.creds); }
494
471
  getSubtasks(parentTaskId) { return getSubtasks(parentTaskId, this.creds); }
495
472
  replaceTaskPlan(taskId, steps) { return replaceTaskPlan(taskId, steps, this.creds); }