@ziggs-ai/ziggs-mcp 0.38.2 → 0.38.3

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 CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.38.3
6
+
7
+ - A plan waiting on your acknowledgement shows under Waiting on you, with Review, in the inbox card and the session card. The same task is no longer also listed as your work. A plan on work someone else hired your assistant for is theirs and is not shown.
8
+
5
9
  ## 0.38.2
6
10
 
7
11
  - Publish with npm provenance attestations from GitHub Actions.
@@ -56,6 +56,7 @@ interface SessionPayload {
56
56
  }>;
57
57
  truncatedProposals?: number;
58
58
  truncatedConnectionRequests?: number;
59
+ truncatedPlanAcks?: number;
59
60
  agreementsListAppUrl?: string;
60
61
  sessionChatCard?: string;
61
62
  notice?: string;
@@ -83,5 +84,5 @@ export declare function registerInboxCardResource(server: McpServer): void;
83
84
  * then it draws whatever `ui/notifications/tool-result` carries, and its
84
85
  * buttons send `ui/open-link`.
85
86
  */
86
- export declare const INBOX_CARD_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Ziggs</title>\n<style>\n :root { --fg:#1d1a20; --muted:#6b6470; --line:#e7e2ea; --bg:#ffffff; --brand:#7c3aed; --chip:#f4f1f6; }\n @media (prefers-color-scheme: dark) { :root { --fg:#f2eef4; --muted:#a59cab; --line:#332d38; --bg:#1b171e; --brand:#a78bfa; --chip:#29232d; } }\n * { box-sizing: border-box; }\n body { margin:0; padding:14px 16px; font:14px/1.45 system-ui,-apple-system,\"Segoe UI\",sans-serif; color:var(--fg); background:var(--bg); }\n h2 { margin:0 0 8px; font-size:12px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); }\n section + section { margin-top:16px; }\n ul { list-style:none; margin:0; padding:0; }\n li { display:flex; align-items:center; gap:12px; padding:9px 0; border-top:1px solid var(--line); }\n li:first-child { border-top:0; }\n .text { flex:1; min-width:0; }\n .title { font-weight:600; overflow-wrap:anywhere; }\n .meta { color:var(--muted); font-size:12px; }\n .chip { display:inline-block; padding:1px 7px; border-radius:999px; background:var(--chip); color:var(--muted); font-size:11px; margin-left:6px; font-weight:500; }\n button { flex:none; min-height:32px; padding:0 12px; border-radius:8px; border:1px solid var(--line); background:transparent; color:var(--fg); font:inherit; font-size:13px; cursor:pointer; }\n button.primary { background:var(--brand); border-color:var(--brand); color:#fff; }\n .empty { color:var(--muted); }\n .all { margin-top:12px; }\n</style>\n</head>\n<body>\n<main id=\"root\"><p class=\"empty\">Loading your Ziggs inbox\u2026</p></main>\n<script>\n(function () {\n var nextId = 1, pending = {}, remeasure = null;\n function send(method, params) {\n var id = nextId++;\n window.parent.postMessage({ jsonrpc: \"2.0\", id: id, method: method, params: params || {} }, \"*\");\n return new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });\n }\n function notify(method, params) {\n window.parent.postMessage({ jsonrpc: \"2.0\", method: method, params: params || {} }, \"*\");\n }\n function el(tag, cls, text) {\n var node = document.createElement(tag);\n if (cls) node.className = cls;\n if (text != null) node.textContent = String(text);\n return node;\n }\n function open(url) {\n if (!url) return;\n send(\"ui/open-link\", { url: url }).catch(function () {});\n }\n function row(title, chip, meta, label, url, primary) {\n var li = el(\"li\"), text = el(\"div\", \"text\"), head = el(\"div\", \"title\", title);\n if (chip) head.appendChild(el(\"span\", \"chip\", chip));\n text.appendChild(head);\n if (meta) text.appendChild(el(\"div\", \"meta\", meta));\n li.appendChild(text);\n if (url) {\n var button = el(\"button\", primary ? \"primary\" : \"\", label);\n button.type = \"button\";\n button.addEventListener(\"click\", function () { open(url); });\n li.appendChild(button);\n }\n return li;\n }\n function kindLabel(kind) {\n return kind === \"link_request\" ? \"Link request\" : kind === \"proposal\" ? \"Proposal\" : \"\";\n }\n function render(data) {\n var root = document.getElementById(\"root\");\n root.textContent = \"\";\n data = data || {};\n var waiting = data.waitingOnYou || [], work = data.yourWork || [], more = data.moreWaiting || 0;\n if (!waiting.length && !more && !work.length) {\n root.appendChild(el(\"p\", \"empty\", \"Nothing is waiting on you in Ziggs.\"));\n }\n if (waiting.length || more) {\n var s1 = el(\"section\"), l1 = el(\"ul\");\n s1.appendChild(el(\"h2\", \"\", \"Waiting on you\"));\n waiting.forEach(function (item) {\n var meta = [item.who, item.when].filter(Boolean).join(\" \u00B7 \");\n l1.appendChild(row(item.title, kindLabel(item.kind), meta, \"Review\", item.url, true));\n });\n s1.appendChild(l1);\n if (more) s1.appendChild(el(\"p\", \"meta\", more + \" more on the website\"));\n root.appendChild(s1);\n }\n if (work.length) {\n var s2 = el(\"section\"), l2 = el(\"ul\");\n s2.appendChild(el(\"h2\", \"\", \"Your work\"));\n work.forEach(function (item) {\n l2.appendChild(row(item.title, null, item.progress ? \"Plan \" + item.progress : \"In progress\", \"Open\", item.url, false));\n });\n s2.appendChild(l2);\n root.appendChild(s2);\n }\n if (data.allUrl) {\n var all = el(\"button\", \"all\", \"Open Ziggs\");\n all.type = \"button\";\n all.addEventListener(\"click\", function () { open(data.allUrl); });\n root.appendChild(all);\n }\n if (remeasure) remeasure();\n }\n window.addEventListener(\"message\", function (event) {\n if (event.source !== window.parent) return;\n var msg = event.data;\n if (!msg || msg.jsonrpc !== \"2.0\") return;\n if (msg.id != null && pending[msg.id] && !msg.method) {\n var waiter = pending[msg.id];\n delete pending[msg.id];\n if (msg.error) waiter.reject(msg.error); else waiter.resolve(msg.result);\n return;\n }\n if (msg.method === \"ui/notifications/tool-result\") {\n render((msg.params || {}).structuredContent);\n }\n });\n // A host sizes the frame from what the view reports. Without a report the\n // frame stays at zero height and the card is drawn but never seen.\n function reportSize() {\n var lastWidth = 0, lastHeight = 0, queued = false;\n function measure() {\n if (queued) return;\n queued = true;\n var next = typeof requestAnimationFrame === \"function\" ? requestAnimationFrame : function (fn) { setTimeout(fn, 16); };\n next(function () {\n queued = false;\n var doc = document.documentElement, previous = doc.style.height;\n doc.style.height = \"max-content\";\n var height = Math.ceil(doc.getBoundingClientRect().height);\n doc.style.height = previous;\n var width = Math.ceil(window.innerWidth);\n if (width === lastWidth && height === lastHeight) return;\n lastWidth = width; lastHeight = height;\n notify(\"ui/notifications/size-changed\", { width: width, height: height });\n });\n }\n remeasure = measure;\n measure();\n if (typeof ResizeObserver === \"function\") {\n var observer = new ResizeObserver(measure);\n observer.observe(document.documentElement);\n observer.observe(document.body);\n }\n }\n send(\"ui/initialize\", {\n protocolVersion: \"2026-01-26\",\n appInfo: { name: \"Ziggs inbox card\", version: \"1\" },\n appCapabilities: {}\n }).then(function () {\n notify(\"ui/notifications/initialized\", {});\n reportSize();\n }, function () {});\n})();\n</script>\n</body>\n</html>\n";
87
+ export declare const INBOX_CARD_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Ziggs</title>\n<style>\n :root { --fg:#1d1a20; --muted:#6b6470; --line:#e7e2ea; --bg:#ffffff; --brand:#7c3aed; --chip:#f4f1f6; }\n @media (prefers-color-scheme: dark) { :root { --fg:#f2eef4; --muted:#a59cab; --line:#332d38; --bg:#1b171e; --brand:#a78bfa; --chip:#29232d; } }\n * { box-sizing: border-box; }\n body { margin:0; padding:14px 16px; font:14px/1.45 system-ui,-apple-system,\"Segoe UI\",sans-serif; color:var(--fg); background:var(--bg); }\n h2 { margin:0 0 8px; font-size:12px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); }\n section + section { margin-top:16px; }\n ul { list-style:none; margin:0; padding:0; }\n li { display:flex; align-items:center; gap:12px; padding:9px 0; border-top:1px solid var(--line); }\n li:first-child { border-top:0; }\n .text { flex:1; min-width:0; }\n .title { font-weight:600; overflow-wrap:anywhere; }\n .meta { color:var(--muted); font-size:12px; }\n .chip { display:inline-block; padding:1px 7px; border-radius:999px; background:var(--chip); color:var(--muted); font-size:11px; margin-left:6px; font-weight:500; }\n button { flex:none; min-height:32px; padding:0 12px; border-radius:8px; border:1px solid var(--line); background:transparent; color:var(--fg); font:inherit; font-size:13px; cursor:pointer; }\n button.primary { background:var(--brand); border-color:var(--brand); color:#fff; }\n .empty { color:var(--muted); }\n .all { margin-top:12px; }\n</style>\n</head>\n<body>\n<main id=\"root\"><p class=\"empty\">Loading your Ziggs inbox\u2026</p></main>\n<script>\n(function () {\n var nextId = 1, pending = {}, remeasure = null;\n function send(method, params) {\n var id = nextId++;\n window.parent.postMessage({ jsonrpc: \"2.0\", id: id, method: method, params: params || {} }, \"*\");\n return new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });\n }\n function notify(method, params) {\n window.parent.postMessage({ jsonrpc: \"2.0\", method: method, params: params || {} }, \"*\");\n }\n function el(tag, cls, text) {\n var node = document.createElement(tag);\n if (cls) node.className = cls;\n if (text != null) node.textContent = String(text);\n return node;\n }\n function open(url) {\n if (!url) return;\n send(\"ui/open-link\", { url: url }).catch(function () {});\n }\n function row(title, chip, meta, label, url, primary) {\n var li = el(\"li\"), text = el(\"div\", \"text\"), head = el(\"div\", \"title\", title);\n if (chip) head.appendChild(el(\"span\", \"chip\", chip));\n text.appendChild(head);\n if (meta) text.appendChild(el(\"div\", \"meta\", meta));\n li.appendChild(text);\n if (url) {\n var button = el(\"button\", primary ? \"primary\" : \"\", label);\n button.type = \"button\";\n button.addEventListener(\"click\", function () { open(url); });\n li.appendChild(button);\n }\n return li;\n }\n function kindLabel(kind) {\n return kind === \"link_request\" ? \"Link request\" : kind === \"proposal\" ? \"Proposal\" : kind === \"plan_ack\" ? \"Plan\" : \"\";\n }\n function render(data) {\n var root = document.getElementById(\"root\");\n root.textContent = \"\";\n data = data || {};\n var waiting = data.waitingOnYou || [], work = data.yourWork || [], more = data.moreWaiting || 0;\n if (!waiting.length && !more && !work.length) {\n root.appendChild(el(\"p\", \"empty\", \"Nothing is waiting on you in Ziggs.\"));\n }\n if (waiting.length || more) {\n var s1 = el(\"section\"), l1 = el(\"ul\");\n s1.appendChild(el(\"h2\", \"\", \"Waiting on you\"));\n waiting.forEach(function (item) {\n var meta = [item.who, item.when].filter(Boolean).join(\" \u00B7 \");\n l1.appendChild(row(item.title, kindLabel(item.kind), meta, \"Review\", item.url, true));\n });\n s1.appendChild(l1);\n if (more) s1.appendChild(el(\"p\", \"meta\", more + \" more on the website\"));\n root.appendChild(s1);\n }\n if (work.length) {\n var s2 = el(\"section\"), l2 = el(\"ul\");\n s2.appendChild(el(\"h2\", \"\", \"Your work\"));\n work.forEach(function (item) {\n l2.appendChild(row(item.title, null, item.progress ? \"Plan \" + item.progress : \"In progress\", \"Open\", item.url, false));\n });\n s2.appendChild(l2);\n root.appendChild(s2);\n }\n if (data.allUrl) {\n var all = el(\"button\", \"all\", \"Open Ziggs\");\n all.type = \"button\";\n all.addEventListener(\"click\", function () { open(data.allUrl); });\n root.appendChild(all);\n }\n if (remeasure) remeasure();\n }\n window.addEventListener(\"message\", function (event) {\n if (event.source !== window.parent) return;\n var msg = event.data;\n if (!msg || msg.jsonrpc !== \"2.0\") return;\n if (msg.id != null && pending[msg.id] && !msg.method) {\n var waiter = pending[msg.id];\n delete pending[msg.id];\n if (msg.error) waiter.reject(msg.error); else waiter.resolve(msg.result);\n return;\n }\n if (msg.method === \"ui/notifications/tool-result\") {\n render((msg.params || {}).structuredContent);\n }\n });\n // A host sizes the frame from what the view reports. Without a report the\n // frame stays at zero height and the card is drawn but never seen.\n function reportSize() {\n var lastWidth = 0, lastHeight = 0, queued = false;\n function measure() {\n if (queued) return;\n queued = true;\n var next = typeof requestAnimationFrame === \"function\" ? requestAnimationFrame : function (fn) { setTimeout(fn, 16); };\n next(function () {\n queued = false;\n var doc = document.documentElement, previous = doc.style.height;\n doc.style.height = \"max-content\";\n var height = Math.ceil(doc.getBoundingClientRect().height);\n doc.style.height = previous;\n var width = Math.ceil(window.innerWidth);\n if (width === lastWidth && height === lastHeight) return;\n lastWidth = width; lastHeight = height;\n notify(\"ui/notifications/size-changed\", { width: width, height: height });\n });\n }\n remeasure = measure;\n measure();\n if (typeof ResizeObserver === \"function\") {\n var observer = new ResizeObserver(measure);\n observer.observe(document.documentElement);\n observer.observe(document.body);\n }\n }\n send(\"ui/initialize\", {\n protocolVersion: \"2026-01-26\",\n appInfo: { name: \"Ziggs inbox card\", version: \"1\" },\n appCapabilities: {}\n }).then(function () {\n notify(\"ui/notifications/initialized\", {});\n reportSize();\n }, function () {});\n})();\n</script>\n</body>\n</html>\n";
87
88
  export {};
package/dist/inboxCard.js CHANGED
@@ -22,7 +22,9 @@ export function inboxCardData(payload) {
22
22
  when: item.proposedAtLabel,
23
23
  url: item.appUrl,
24
24
  })),
25
- moreWaiting: (payload.truncatedProposals ?? 0) + (payload.truncatedConnectionRequests ?? 0),
25
+ moreWaiting: (payload.truncatedProposals ?? 0) +
26
+ (payload.truncatedConnectionRequests ?? 0) +
27
+ (payload.truncatedPlanAcks ?? 0),
26
28
  yourWork: (payload.activeWork ?? []).map((item) => ({
27
29
  title: item.title,
28
30
  progress: item.planTotal > 0 ? `${item.planDone}/${item.planTotal}` : null,
@@ -132,7 +134,7 @@ export const INBOX_CARD_HTML = `<!doctype html>
132
134
  return li;
133
135
  }
134
136
  function kindLabel(kind) {
135
- return kind === "link_request" ? "Link request" : kind === "proposal" ? "Proposal" : "";
137
+ return kind === "link_request" ? "Link request" : kind === "proposal" ? "Proposal" : kind === "plan_ack" ? "Plan" : "";
136
138
  }
137
139
  function render(data) {
138
140
  var root = document.getElementById("root");
@@ -1,7 +1,7 @@
1
1
  export { resolveWebAppOrigin, agreementAppUrl, agreementsListAppUrl, connectionsSettingsAppUrl, walletAppUrl } from '@ziggs-ai/api-client';
2
2
  import { type DecisionOutcome, type InboxEnvelope, type InboxHumanAttention, type InboxTaskRef, type Task } from '@ziggs-ai/api-client';
3
3
  import type { ReadPlanCall } from './inboxToolResult.js';
4
- export type PendingDecisionKind = 'proposal' | 'link_request';
4
+ export type PendingDecisionKind = 'proposal' | 'link_request' | 'plan_ack';
5
5
  /**
6
6
  * A reference to a decision somebody is waiting on. Who may answer it is not
7
7
  * known here: the inbox lists slots, and a slot is not authority. The
@@ -9,8 +9,12 @@ export type PendingDecisionKind = 'proposal' | 'link_request';
9
9
  */
10
10
  export interface PendingDecisionItem {
11
11
  kind: PendingDecisionKind;
12
- /** Agreement id for proposals; requestId for link requests. */
12
+ /**
13
+ * Agreement id for proposals and plans; requestId for link requests.
14
+ */
13
15
  agreementId: string;
16
+ /** The task whose plan waits, on a `plan_ack`. */
17
+ taskId?: string;
14
18
  title: string;
15
19
  subtitle: string | null;
16
20
  proposedAt: string | null;
@@ -107,6 +111,7 @@ export declare function buildActiveWorkItemsFromInbox(refs: InboxTaskRef[], webO
107
111
  export declare function buildSessionChatCard(decisions: PendingDecisionItem[], work: ActiveWorkItem[], opts: {
108
112
  truncatedProposals?: number;
109
113
  truncatedConnectionRequests?: number;
114
+ truncatedPlanAcks?: number;
110
115
  agreementsListAppUrl?: string;
111
116
  }): string;
112
117
  /** Structured session payload for MCP tools ( + active work). */
@@ -181,8 +181,27 @@ function linkToItem(c, origin) {
181
181
  proposedAt: c.requestedAt,
182
182
  }, origin);
183
183
  }
184
+ /**
185
+ * A plan waiting on the person. The plan is acknowledged signed in on the
186
+ * agreement page, where the task and its plan are shown, so Review goes there.
187
+ */
188
+ function planAckToItem(a, origin) {
189
+ return {
190
+ ...decisionToItem('plan_ack', a.agreementId, {
191
+ title: displayTitle(a.title?.trim() || '(untitled task)'),
192
+ subtitle: a.scope === 'mid_work'
193
+ ? 'The plan changed during the work.'
194
+ : 'Work starts once you acknowledge the plan.',
195
+ proposedAt: a.updatedAt,
196
+ }, origin),
197
+ taskId: a.taskId,
198
+ };
199
+ }
184
200
  export function buildPendingDecisionItems(inbox, webOrigin) {
185
201
  const items = [];
202
+ for (const a of inbox.planAcksAwaitingMe ?? []) {
203
+ items.push(planAckToItem(a, webOrigin));
204
+ }
186
205
  for (const p of inbox.proposalsAwaitingMe ?? []) {
187
206
  items.push(proposalToItem(p, webOrigin));
188
207
  }
@@ -263,18 +282,24 @@ export function buildActiveWorkItemsFromInbox(refs, webOrigin) {
263
282
  function kindLabel(kind) {
264
283
  if (kind === 'proposal')
265
284
  return 'Agreement proposal';
285
+ if (kind === 'plan_ack')
286
+ return 'Plan to acknowledge';
266
287
  return 'Agent link request';
267
288
  }
268
289
  function kindHint(kind) {
269
290
  if (kind === 'proposal') {
270
291
  return 'Someone proposed work or terms — your approval opens or rejects it.';
271
292
  }
293
+ if (kind === 'plan_ack') {
294
+ return 'Work you are paying for has a plan waiting on you.';
295
+ }
272
296
  return 'Another agent wants to link — your approval enables cross-org reach.';
273
297
  }
274
298
  function buildDecisionSection(items, opts) {
275
299
  const truncatedProposals = opts.truncatedProposals ?? 0;
276
300
  const truncatedConnectionRequests = opts.truncatedConnectionRequests ?? 0;
277
- const truncated = truncatedProposals + truncatedConnectionRequests;
301
+ const truncatedPlanAcks = opts.truncatedPlanAcks ?? 0;
302
+ const truncated = truncatedProposals + truncatedConnectionRequests + truncatedPlanAcks;
278
303
  if (!items.length && truncated === 0)
279
304
  return [];
280
305
  const lines = [];
@@ -287,7 +312,7 @@ function buildDecisionSection(items, opts) {
287
312
  lines.push('');
288
313
  lines.push(`**${item.title}**`);
289
314
  lines.push('');
290
- lines.push(`\`${item.agreementId}\`${item.proposedAtLabel ? ` · ${item.proposedAtLabel}` : ''}`);
315
+ lines.push(`\`${item.taskId ?? item.agreementId}\`${item.proposedAtLabel ? ` · ${item.proposedAtLabel}` : ''}`);
291
316
  lines.push('');
292
317
  lines.push(`_${kindHint(item.kind)}_`);
293
318
  if (item.subtitle) {
@@ -297,15 +322,20 @@ function buildDecisionSection(items, opts) {
297
322
  lines.push('');
298
323
  lines.push(`[Review in Ziggs →](${item.appUrl})`);
299
324
  lines.push('');
300
- // No pre-filled respond call: a slot is not authority, and a call that
301
- // 403s every time is worse than a line that says where the answer is.
302
- lines.push('Review the terms here. The agent reads approvalAction on the agreement to check whether an existing grant lets it decide; otherwise the accountable party decides in Ziggs.');
325
+ if (item.kind === 'plan_ack') {
326
+ lines.push('The person acknowledges the plan in Ziggs, signed in as themselves.');
327
+ }
328
+ else {
329
+ // No pre-filled respond call: a slot is not authority, and a call that
330
+ // 403s every time is worse than a line that says where the answer is.
331
+ lines.push('Review the terms here. The agent reads approvalAction on the agreement to check whether an existing grant lets it decide; otherwise the accountable party decides in Ziggs.');
332
+ }
303
333
  lines.push('');
304
334
  }
305
335
  if (truncated > 0) {
306
336
  lines.push('---');
307
337
  lines.push('');
308
- lines.push(`_+${truncated} more pending (${truncatedProposals} proposal(s), ${truncatedConnectionRequests} link(s)) — not listed here._`);
338
+ lines.push(`_+${truncated} more pending (${truncatedProposals} proposal(s), ${truncatedConnectionRequests} link(s)${truncatedPlanAcks ? `, ${truncatedPlanAcks} plan(s)` : ''}) — not listed here._`);
309
339
  lines.push('');
310
340
  }
311
341
  return lines;
@@ -352,7 +382,9 @@ function buildWorkSection(work, startIndex = 1) {
352
382
  export function buildSessionChatCard(decisions, work, opts) {
353
383
  const truncatedProposals = opts.truncatedProposals ?? 0;
354
384
  const truncatedConnectionRequests = opts.truncatedConnectionRequests ?? 0;
355
- const truncated = truncatedProposals + truncatedConnectionRequests;
385
+ const truncatedPlanAcks = opts.truncatedPlanAcks ?? 0;
386
+ const truncated = truncatedProposals + truncatedConnectionRequests + truncatedPlanAcks;
387
+ const planAckCount = decisions.filter((d) => d.kind === 'plan_ack').length;
356
388
  const decisionListed = decisions.length + truncated;
357
389
  const workCount = work.length;
358
390
  const total = decisionListed + workCount;
@@ -365,9 +397,14 @@ export function buildSessionChatCard(decisions, work, opts) {
365
397
  '|:--|--:|',
366
398
  ...(decisionListed > 0
367
399
  ? [
368
- `| Approve / reject | **${decisionListed}** |`,
400
+ `| ${planAckCount + truncatedPlanAcks > 0 ? 'Approve / reject / acknowledge' : 'Approve / reject'} | **${decisionListed}** |`,
369
401
  `| — proposals | ${decisions.filter((d) => d.kind === 'proposal').length}${truncatedProposals ? ` (+${truncatedProposals} hidden)` : ''} |`,
370
402
  `| — link requests | ${decisions.filter((d) => d.kind === 'link_request').length}${truncatedConnectionRequests ? ` (+${truncatedConnectionRequests} hidden)` : ''} |`,
403
+ ...(planAckCount + truncatedPlanAcks > 0
404
+ ? [
405
+ `| — plans to acknowledge | ${planAckCount}${truncatedPlanAcks ? ` (+${truncatedPlanAcks} hidden)` : ''} |`,
406
+ ]
407
+ : []),
371
408
  ]
372
409
  : []),
373
410
  ...(workCount > 0 ? [`| Active tasks (your work) | **${workCount}** |`] : []),
@@ -393,18 +430,26 @@ export function buildSessionChatCard(decisions, work, opts) {
393
430
  export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
394
431
  const withSessionCard = opts?.withSessionCard !== false;
395
432
  const decisions = buildPendingDecisionItems(inbox, webOrigin);
396
- const activeWork = opts?.activeTasks !== undefined
433
+ // A task whose plan waits on the person is theirs to acknowledge before it
434
+ // is anyone's work, so it is listed once, as the decision.
435
+ const waitingPlans = new Set((inbox.planAcksAwaitingMe ?? []).map((a) => a.taskId));
436
+ const activeWork = (opts?.activeTasks !== undefined
397
437
  ? buildActiveWorkItems(opts.activeTasks, webOrigin)
398
- : buildActiveWorkItemsFromInbox(inbox.tasksAwaitingMe ?? [], webOrigin);
438
+ : buildActiveWorkItemsFromInbox(inbox.tasksAwaitingMe ?? [], webOrigin)).filter((w) => !waitingPlans.has(w.taskId));
399
439
  const truncatedProposals = inbox.truncatedProposals ?? 0;
400
440
  const truncatedConnectionRequests = inbox.truncatedConnectionRequests ?? 0;
401
- const pendingCount = decisions.length + truncatedProposals + truncatedConnectionRequests;
441
+ const truncatedPlanAcks = inbox.truncatedPlanAcks ?? 0;
442
+ const pendingCount = decisions.length +
443
+ truncatedProposals +
444
+ truncatedConnectionRequests +
445
+ truncatedPlanAcks;
402
446
  const activeWorkCount = activeWork.length;
403
447
  const actionCount = pendingCount + activeWorkCount;
404
448
  const listUrl = agreementsListAppUrl(webOrigin);
405
449
  const cardOpts = {
406
450
  truncatedProposals,
407
451
  truncatedConnectionRequests,
452
+ truncatedPlanAcks,
408
453
  agreementsListAppUrl: listUrl,
409
454
  };
410
455
  // Only the session-start read ships it. The long-poll shape passes
@@ -415,6 +460,7 @@ export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
415
460
  : null;
416
461
  const proposalCount = decisions.filter((d) => d.kind === 'proposal').length;
417
462
  const linkCount = decisions.filter((d) => d.kind === 'link_request').length;
463
+ const planAckCount = decisions.filter((d) => d.kind === 'plan_ack').length;
418
464
  // Decisions somebody made on requests this side made. Counted here and named
419
465
  // in the line the model reads; the entries themselves ride the news half of
420
466
  // the inbox result (`decidedForMe`), which the cold read already carries.
@@ -444,7 +490,7 @@ export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
444
490
  : 'No pending decisions or active tasks.');
445
491
  }
446
492
  else {
447
- parts.push(`${count(pendingCount, 'pending decision', 'pending decisions')} (${count(proposalCount + truncatedProposals, 'proposal', 'proposals')}, ${count(linkCount + truncatedConnectionRequests, 'link request', 'link requests')}) and ${count(activeWorkCount, 'active task', 'active tasks')}.`);
493
+ parts.push(`${count(pendingCount, 'pending decision', 'pending decisions')} (${count(proposalCount + truncatedProposals, 'proposal', 'proposals')}, ${count(linkCount + truncatedConnectionRequests, 'link request', 'link requests')}${planAckCount + truncatedPlanAcks > 0 ? `, ${count(planAckCount + truncatedPlanAcks, 'plan to acknowledge', 'plans to acknowledge')}` : ''}) and ${count(activeWorkCount, 'active task', 'active tasks')}.`);
448
494
  }
449
495
  if (decidedNote)
450
496
  parts.push(decidedNote);
@@ -464,9 +510,10 @@ export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
464
510
  summary: {
465
511
  proposals: proposalCount + truncatedProposals,
466
512
  linkRequests: linkCount + truncatedConnectionRequests,
513
+ planAcks: planAckCount + truncatedPlanAcks,
467
514
  activeTasks: activeWorkCount,
468
515
  listed: decisions.length,
469
- truncated: truncatedProposals + truncatedConnectionRequests,
516
+ truncated: truncatedProposals + truncatedConnectionRequests + truncatedPlanAcks,
470
517
  decidedForMe: decidedForMe.filter((i) => i.outcome !== 'held').length,
471
518
  refusals: decidedForMe.filter((i) => i.outcome === 'rejected').length,
472
519
  },
@@ -474,6 +521,7 @@ export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
474
521
  activeWork,
475
522
  truncatedProposals,
476
523
  truncatedConnectionRequests,
524
+ truncatedPlanAcks,
477
525
  agreementsListAppUrl: listUrl,
478
526
  connectionsSettingsAppUrl: connectionsSettingsAppUrl(webOrigin),
479
527
  // one card per session start. The owner (withSessionCard) embeds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.38.2",
3
+ "version": "0.38.3",
4
4
  "description": "MCP server for Claude Code, Cursor, and other MCP hosts — act as your Ziggs delegate agent",
5
5
  "homepage": "https://ziggsai.com/docs",
6
6
  "bugs": {