@agent-deck/backend 1.2.9 → 1.2.10

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.
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AgentDeckContextError = void 0;
4
4
  exports.resolveAgentDeckId = resolveAgentDeckId;
5
5
  const shared_1 = require("@agent-deck/shared");
6
- const repo_deck_1 = require("../scope/repo-deck");
7
6
  class AgentDeckContextError extends Error {
8
7
  constructor(message) {
9
8
  super(message);
@@ -15,34 +14,16 @@ function headerValue(request, name) {
15
14
  const value = request.headers[name];
16
15
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
17
16
  }
18
- /** Resolve which deck an agent request is scoped to (workspace manifest or explicit deck id). */
17
+ /** Resolve which deck an agent request is scoped to (session deck id header only). */
19
18
  async function resolveAgentDeckId(request, db) {
20
19
  const deckIdHeader = headerValue(request, shared_1.AGENT_DECK_DECK_ID_HEADER);
21
- if (deckIdHeader) {
22
- const deck = await db.getDeck(deckIdHeader);
23
- if (!deck) {
24
- throw new AgentDeckContextError(`Deck not found: ${deckIdHeader}`);
25
- }
26
- return deckIdHeader;
20
+ if (!deckIdHeader) {
21
+ throw new AgentDeckContextError('No bound deck call bind_workspace with deckId (use get_decks to list decks) or switch_bound_deck');
27
22
  }
28
- const workspaceRoot = headerValue(request, shared_1.AGENT_DECK_WORKSPACE_HEADER);
29
- if (workspaceRoot) {
30
- let manifest;
31
- try {
32
- manifest = await (0, repo_deck_1.loadRepoDeckManifest)(workspaceRoot);
33
- }
34
- catch (error) {
35
- throw new AgentDeckContextError(error instanceof repo_deck_1.RepoDeckManifestError ? error.message : 'Failed to load repo deck manifest');
36
- }
37
- if (!manifest) {
38
- throw new AgentDeckContextError(`No ${'.agent-deck/deck.yaml'} found in workspace: ${workspaceRoot}`);
39
- }
40
- const deck = await db.getDeck(manifest.deck_id);
41
- if (!deck) {
42
- throw new AgentDeckContextError(`deck_id in repo manifest not found in Agent Deck: ${manifest.deck_id}`);
43
- }
44
- return manifest.deck_id;
23
+ const deck = await db.getDeck(deckIdHeader);
24
+ if (!deck) {
25
+ throw new AgentDeckContextError(`Deck not found: ${deckIdHeader}`);
45
26
  }
46
- throw new AgentDeckContextError('Agent deck context required: set x-agent-deck-workspace (with .agent-deck/deck.yaml) or x-agent-deck-deck-id');
27
+ return deckIdHeader;
47
28
  }
48
29
  //# sourceMappingURL=agent-deck-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-deck-context.js","sourceRoot":"","sources":["../../src/lib/agent-deck-context.ts"],"names":[],"mappings":";;;AAqBA,gDA2CC;AA/DD,+CAG4B;AAE5B,kDAAiF;AAEjF,MAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AALD,sDAKC;AAED,SAAS,WAAW,CAAC,OAAuB,EAAE,IAAY;IACxD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9E,CAAC;AAED,iGAAiG;AAC1F,KAAK,UAAU,kBAAkB,CACtC,OAAuB,EACvB,EAAmB;IAEnB,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,EAAE,kCAAyB,CAAC,CAAC;IACrE,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,qBAAqB,CAAC,mBAAmB,YAAY,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,EAAE,oCAA2B,CAAC,CAAC;IACxE,IAAI,aAAa,EAAE,CAAC;QAClB,IAAI,QAAQ,CAAC;QACb,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAA,gCAAoB,EAAC,aAAa,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,qBAAqB,CAC7B,KAAK,YAAY,iCAAqB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,mCAAmC,CAC7F,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,qBAAqB,CAC7B,MAAM,uBAAuB,wBAAwB,aAAa,EAAE,CACrE,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,qBAAqB,CAC7B,qDAAqD,QAAQ,CAAC,OAAO,EAAE,CACxE,CAAC;QACJ,CAAC;QAED,OAAO,QAAQ,CAAC,OAAO,CAAC;IAC1B,CAAC;IAED,MAAM,IAAI,qBAAqB,CAC7B,8GAA8G,CAC/G,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"agent-deck-context.js","sourceRoot":"","sources":["../../src/lib/agent-deck-context.ts"],"names":[],"mappings":";;;AAiBA,gDAgBC;AAhCD,+CAA+D;AAG/D,MAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AALD,sDAKC;AAED,SAAS,WAAW,CAAC,OAAuB,EAAE,IAAY;IACxD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9E,CAAC;AAED,sFAAsF;AAC/E,KAAK,UAAU,kBAAkB,CACtC,OAAuB,EACvB,EAAmB;IAEnB,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,EAAE,kCAAyB,CAAC,CAAC;IACrE,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,qBAAqB,CAC7B,oGAAoG,CACrG,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,qBAAqB,CAAC,mBAAmB,YAAY,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC"}
@@ -9,6 +9,9 @@ export declare class AgentDeckMCPServer {
9
9
  /** Per-session workspace + optional deck override (see mcp-session-binding.ts). */
10
10
  private sessionBinding;
11
11
  private activeSessionId;
12
+ /** Session badge from the backend registry (POST /api/scope/live-display response). */
13
+ private badgeBySession;
14
+ private lastTouchAtMs;
12
15
  private get server();
13
16
  constructor(port?: number, backendUrl?: string);
14
17
  private createMcpServer;
@@ -17,6 +20,9 @@ export declare class AgentDeckMCPServer {
17
20
  private fetchDeck;
18
21
  private buildBindingPayload;
19
22
  private registerLiveDisplay;
23
+ private static readonly TOUCH_DEBOUNCE_MS;
24
+ /** Fire-and-forget lastActivityAt bump; only for sessions the registry knows. */
25
+ private touchLiveDisplay;
20
26
  private unregisterLiveDisplay;
21
27
  private callBackendAPI;
22
28
  private getBoundDeckId;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAUA,OAAO,OAA8B,MAAM,SAAS,CAAC;AA0BrD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAiC;IACjD,mFAAmF;IACnF,OAAO,CAAC,wBAAwB,CAAwB;IACxD,mFAAmF;IACnF,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,eAAe,CAAqB;IAE5C,OAAO,KAAK,MAAM,GAKjB;gBAEW,IAAI,GAAE,MAAa,EAAE,UAAU,GAAE,MAAgC;IAc7E,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;YAIT,SAAS;YAkBT,mBAAmB;YAYnB,mBAAmB;YA0BnB,qBAAqB;YAUrB,cAAc;YAwCd,cAAc;IAQ5B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,SAAS;IAIjB,6EAA6E;IAC7E,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,UAAU;IAy2BlB,OAAO,CAAC,cAAc;IA8MtB,OAAO,CAAC,WAAW;IA2CnB,OAAO,CAAC,kBAAkB;YAKZ,aAAa;YAwDb,uBAAuB;IAiB/B,KAAK;IAmCL,IAAI;CAeX"}
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AASA,OAAO,OAA8B,MAAM,SAAS,CAAC;AAmBrD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAiC;IACjD,mFAAmF;IACnF,OAAO,CAAC,wBAAwB,CAAwB;IACxD,mFAAmF;IACnF,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,eAAe,CAAqB;IAC5C,uFAAuF;IACvF,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,aAAa,CAA6B;IAElD,OAAO,KAAK,MAAM,GAKjB;gBAEW,IAAI,GAAE,MAAa,EAAE,UAAU,GAAE,MAAgC;IAc7E,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;YAIT,SAAS;YAkBT,mBAAmB;YAgBnB,mBAAmB;IA+BjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAElD,iFAAiF;IACjF,OAAO,CAAC,gBAAgB;YAmBV,qBAAqB;YAUrB,cAAc;YAwCd,cAAc;IAQ5B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,SAAS;IAIjB,6EAA6E;IAC7E,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,UAAU;IAkpBlB,OAAO,CAAC,cAAc;IA8MtB,OAAO,CAAC,WAAW;IA2CnB,OAAO,CAAC,kBAAkB;YAKZ,aAAa;YA2Db,uBAAuB;IAkB/B,KAAK;IAiCL,IAAI;CAeX"}
@@ -10,10 +10,7 @@ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
10
10
  const shared_1 = require("@agent-deck/shared");
11
11
  const express_1 = __importDefault(require("express"));
12
12
  const node_crypto_1 = require("node:crypto");
13
- const promises_1 = __importDefault(require("node:fs/promises"));
14
- const node_path_1 = __importDefault(require("node:path"));
15
13
  const zod_1 = require("zod");
16
- const repo_deck_1 = require("./scope/repo-deck");
17
14
  const version_1 = require("./lib/version");
18
15
  const mcp_session_binding_1 = require("./mcp-session-binding");
19
16
  const agentClientHeaders = {
@@ -30,6 +27,9 @@ class AgentDeckMCPServer {
30
27
  /** Per-session workspace + optional deck override (see mcp-session-binding.ts). */
31
28
  sessionBinding;
32
29
  activeSessionId;
30
+ /** Session badge from the backend registry (POST /api/scope/live-display response). */
31
+ badgeBySession = new Map();
32
+ lastTouchAtMs = new Map();
33
33
  get server() {
34
34
  if (!this.mcpServerForRegistration) {
35
35
  throw new Error('Internal: MCP server not in registration context');
@@ -77,18 +77,22 @@ class AgentDeckMCPServer {
77
77
  }
78
78
  return body.data;
79
79
  }
80
- async buildBindingPayload(sessionId, manifestDeckId) {
80
+ async buildBindingPayload(sessionId) {
81
81
  const snapshot = this.sessionBinding.getBinding(sessionId);
82
82
  const deck = await this.callBackendAPI('/api/scope/deck');
83
+ const badge = this.badgeBySession.get(sessionId);
84
+ const cardCounts = deck ? (0, shared_1.countDeckCards)(deck) : { mcp: 0, credentials: 0, playbooks: 0 };
83
85
  return {
84
86
  workspaceRoot: snapshot.workspaceRoot,
85
87
  deck_id: deck?.id,
86
88
  deck_name: deck?.name,
87
- deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot, manifestDeckId),
89
+ deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot),
88
90
  session_deck_override: this.sessionBinding.hasSessionDeckOverride(sessionId),
91
+ badge,
92
+ display_summary: (0, shared_1.formatDisplayLine)(deck?.name ?? null, cardCounts, { badge }),
89
93
  };
90
94
  }
91
- async registerLiveDisplay(sessionId, manifestDeckId) {
95
+ async registerLiveDisplay(sessionId) {
92
96
  const snapshot = this.sessionBinding.getBinding(sessionId);
93
97
  if (!snapshot.workspaceRoot) {
94
98
  return;
@@ -97,7 +101,8 @@ class AgentDeckMCPServer {
97
101
  if (!deck?.id || !deck?.name) {
98
102
  return;
99
103
  }
100
- await this.callBackendAPI('/api/scope/live-display', {
104
+ const clientName = this.sessions.get(sessionId)?.server.server.getClientVersion()?.name;
105
+ const result = await this.callBackendAPI('/api/scope/live-display', {
101
106
  method: 'POST',
102
107
  headers: { 'Content-Type': 'application/json' },
103
108
  body: JSON.stringify({
@@ -105,11 +110,32 @@ class AgentDeckMCPServer {
105
110
  workspaceRoot: snapshot.workspaceRoot,
106
111
  deckId: deck.id,
107
112
  deckName: deck.name,
108
- source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot, manifestDeckId),
113
+ source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot),
114
+ clientName,
109
115
  cardCounts: (0, shared_1.countDeckCards)(deck),
110
116
  updatedAt: new Date().toISOString(),
111
117
  }),
112
118
  });
119
+ if (result && typeof result.badge === 'string') {
120
+ this.badgeBySession.set(sessionId, result.badge);
121
+ }
122
+ }
123
+ static TOUCH_DEBOUNCE_MS = 5_000;
124
+ /** Fire-and-forget lastActivityAt bump; only for sessions the registry knows. */
125
+ touchLiveDisplay(sessionId) {
126
+ if (!this.badgeBySession.has(sessionId)) {
127
+ return;
128
+ }
129
+ const now = Date.now();
130
+ if (now - (this.lastTouchAtMs.get(sessionId) ?? 0) < AgentDeckMCPServer.TOUCH_DEBOUNCE_MS) {
131
+ return;
132
+ }
133
+ this.lastTouchAtMs.set(sessionId, now);
134
+ void this.callBackendAPI(`/api/scope/live-display/${encodeURIComponent(sessionId)}/touch`, {
135
+ method: 'POST',
136
+ headers: { 'Content-Type': 'application/json' },
137
+ body: JSON.stringify({ at: new Date().toISOString() }),
138
+ }).catch(() => { });
113
139
  }
114
140
  async unregisterLiveDisplay(sessionId) {
115
141
  try {
@@ -182,84 +208,52 @@ class AgentDeckMCPServer {
182
208
  setupTools() {
183
209
  this.registerTool("bind_workspace", {
184
210
  title: "Bind Workspace",
185
- description: "Bind this MCP session to a workspace root. Pass deckId for a session-only deck override (same path, different concurrent agents). Without deckId, uses .agent-deck/deck.yaml when present.",
211
+ description: "Bind this MCP session to a workspace root and deck. Use get_decks to list deck ids.",
186
212
  inputSchema: {
187
213
  workspaceRoot: zod_1.z.string(),
188
- deckId: zod_1.z.string().uuid().optional(),
214
+ deckId: zod_1.z.string().uuid(),
189
215
  },
190
216
  }, async ({ workspaceRoot, deckId }) => {
191
217
  try {
192
218
  const sessionId = this.getSessionId();
193
219
  this.sessionBinding.setWorkspace(sessionId, workspaceRoot);
194
- if (deckId) {
195
- const deck = await this.fetchDeck(deckId);
196
- this.sessionBinding.setDeckId(sessionId, deckId);
197
- const payload = await this.buildBindingPayload(sessionId);
198
- await this.registerLiveDisplay(sessionId);
199
- return {
200
- content: [{
201
- type: "text",
202
- text: JSON.stringify({
203
- ...payload,
204
- deck_name: deck.name,
205
- message: 'Session bound with deck override. .agent-deck/deck.yaml is unchanged; other MCP sessions can use a different deck at the same path.',
206
- }, null, 2),
207
- }],
208
- };
209
- }
210
- this.sessionBinding.clearDeckId(sessionId);
211
- const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
212
- method: 'POST',
213
- headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
214
- body: JSON.stringify({ workspaceRoot }),
215
- });
216
- const body = (await resolved.json());
217
- if (!body.success || !body.data) {
218
- throw new Error(body.error ??
219
- 'Failed to resolve workspace deck. Pass deckId to bind without deck.yaml, or call setup_repo_deck.');
220
- }
221
- const { data } = body;
222
- const payload = await this.buildBindingPayload(sessionId, data.manifest.deck_id);
223
- await this.registerLiveDisplay(sessionId, data.manifest.deck_id);
220
+ const deck = await this.fetchDeck(deckId);
221
+ this.sessionBinding.setDeckId(sessionId, deckId);
222
+ await this.registerLiveDisplay(sessionId);
223
+ const payload = await this.buildBindingPayload(sessionId);
224
224
  return {
225
225
  content: [{
226
226
  type: "text",
227
227
  text: JSON.stringify({
228
228
  ...payload,
229
- manifestPath: data.manifestPath,
230
- message: 'Session bound via .agent-deck/deck.yaml.',
229
+ deck_name: deck.name,
230
+ message: 'Session bound to deck.',
231
231
  }, null, 2),
232
232
  }],
233
233
  };
234
234
  }
235
235
  catch (error) {
236
- const message = String(error);
237
- const hint = message.includes('deck.yaml') || message.includes('No .agent-deck')
238
- ? ' Pass deckId on bind_workspace, call setup_repo_deck, or get_repo_deck_status to diagnose.'
239
- : '';
240
- return {
241
- content: [{ type: "text", text: JSON.stringify({ error: message + hint }, null, 2) }],
242
- };
236
+ return this.toolError(error);
243
237
  }
244
238
  });
245
239
  this.registerTool("switch_bound_deck", {
246
240
  title: "Switch Bound Deck",
247
- description: "Switch the deck for this MCP session only. Does not modify .agent-deck/deck.yaml. Use when multiple agent sessions share the same workspace path.",
241
+ description: "Switch the deck for this MCP session only. Use when multiple agent sessions share the same workspace path.",
248
242
  inputSchema: { deckId: zod_1.z.string().uuid() },
249
243
  }, async ({ deckId }) => {
250
244
  try {
251
245
  const sessionId = this.getSessionId();
252
246
  const deck = await this.fetchDeck(deckId);
253
247
  this.sessionBinding.setDeckId(sessionId, deckId);
254
- const payload = await this.buildBindingPayload(sessionId);
255
248
  await this.registerLiveDisplay(sessionId);
249
+ const payload = await this.buildBindingPayload(sessionId);
256
250
  return {
257
251
  content: [{
258
252
  type: "text",
259
253
  text: JSON.stringify({
260
254
  ...payload,
261
255
  deck_name: deck.name,
262
- message: 'Session deck switched. Other sessions and deck.yaml are unchanged.',
256
+ message: 'Session deck switched. Other sessions are unchanged.',
263
257
  }, null, 2),
264
258
  }],
265
259
  };
@@ -270,31 +264,27 @@ class AgentDeckMCPServer {
270
264
  });
271
265
  this.registerTool("get_session_binding", {
272
266
  title: "Get Session Binding",
273
- description: "Show workspace and effective deck for this MCP session, including whether the deck comes from a session override or repo deck.yaml.",
267
+ description: "Show workspace and effective deck for this MCP session (session override or env default).",
274
268
  inputSchema: {},
275
269
  }, async () => {
276
270
  try {
277
271
  const sessionId = this.getSessionId();
278
272
  const snapshot = this.sessionBinding.getBinding(sessionId);
279
- let manifestDeckId;
280
- if (snapshot.workspaceRoot && !snapshot.deckSource) {
281
- const manifest = await (0, repo_deck_1.loadRepoDeckManifest)(snapshot.workspaceRoot);
282
- manifestDeckId = manifest?.deck_id;
283
- }
284
273
  const deck = await this.callBackendAPI('/api/scope/deck');
274
+ const badge = this.badgeBySession.get(sessionId);
285
275
  const cardCounts = deck ? (0, shared_1.countDeckCards)(deck) : { mcp: 0, credentials: 0, playbooks: 0 };
286
- const displaySummary = (0, shared_1.formatDisplayLine)(deck?.name ?? null, cardCounts);
276
+ const displaySummary = (0, shared_1.formatDisplayLine)(deck?.name ?? null, cardCounts, { badge });
287
277
  return {
288
278
  content: [{
289
279
  type: "text",
290
280
  text: JSON.stringify({
291
281
  workspaceRoot: snapshot.workspaceRoot,
292
- session_deck_override: snapshot.deckId,
282
+ session_deck_id: snapshot.deckId,
293
283
  session_deck_source: snapshot.deckSource,
294
- repo_manifest_deck_id: manifestDeckId,
295
284
  effective_deck_id: deck?.id,
296
285
  effective_deck_name: deck?.name,
297
- effective_deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot, manifestDeckId),
286
+ effective_deck_source: (0, mcp_session_binding_1.resolveDeckBindingSource)(snapshot),
287
+ badge,
298
288
  display_summary: displaySummary,
299
289
  }, null, 2),
300
290
  }],
@@ -304,147 +294,6 @@ class AgentDeckMCPServer {
304
294
  return this.toolError(error);
305
295
  }
306
296
  });
307
- this.registerTool("get_repo_deck_status", {
308
- title: "Get Repo Deck Status",
309
- description: "Check whether .agent-deck/deck.yaml exists in a workspace and whether it links to a valid deck",
310
- inputSchema: { workspaceRoot: zod_1.z.string() },
311
- }, async ({ workspaceRoot }) => {
312
- try {
313
- const filePath = (0, repo_deck_1.repoDeckManifestFilePath)(workspaceRoot);
314
- const manifest = await (0, repo_deck_1.loadRepoDeckManifest)(workspaceRoot);
315
- if (!manifest) {
316
- const decks = await this.callBackendAPI('/api/decks');
317
- return {
318
- content: [{
319
- type: "text",
320
- text: JSON.stringify({
321
- status: 'missing',
322
- workspaceRoot,
323
- expectedPath: shared_1.REPO_DECK_MANIFEST_PATH,
324
- message: 'No .agent-deck/deck.yaml found. Use setup_repo_deck to create one.',
325
- availableDecks: Array.isArray(decks)
326
- ? decks.map((d) => ({ id: d.id, name: d.name }))
327
- : decks,
328
- }, null, 2),
329
- }],
330
- };
331
- }
332
- const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
333
- method: 'POST',
334
- headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
335
- body: JSON.stringify({ workspaceRoot }),
336
- });
337
- const body = (await resolved.json());
338
- return {
339
- content: [{
340
- type: "text",
341
- text: JSON.stringify({
342
- status: body.success ? 'ok' : 'invalid',
343
- workspaceRoot,
344
- filePath,
345
- manifest,
346
- deck: body.data?.deck,
347
- error: body.success ? undefined : body.error,
348
- }, null, 2),
349
- }],
350
- };
351
- }
352
- catch (error) {
353
- return {
354
- content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }],
355
- };
356
- }
357
- });
358
- this.registerTool("setup_repo_deck", {
359
- title: "Setup Repo Deck",
360
- description: "Create or verify .agent-deck/deck.yaml in a repo. Links the workspace to an Agent Deck by deck_id. Optionally writes the file.",
361
- inputSchema: {
362
- workspaceRoot: zod_1.z.string(),
363
- deckId: zod_1.z.string().uuid().optional(),
364
- deckName: zod_1.z.string().optional(),
365
- writeFile: zod_1.z.boolean().optional(),
366
- },
367
- }, async ({ workspaceRoot, deckId, deckName, writeFile = true }) => {
368
- try {
369
- const sessionId = this.getSessionId();
370
- const existing = await (0, repo_deck_1.loadRepoDeckManifest)(workspaceRoot);
371
- if (existing) {
372
- this.sessionBinding.setWorkspace(sessionId, workspaceRoot);
373
- const resolved = await fetch(`${this.backendUrl}/api/scope/resolve`, {
374
- method: 'POST',
375
- headers: { ...agentClientHeaders, 'Content-Type': 'application/json' },
376
- body: JSON.stringify({ workspaceRoot }),
377
- });
378
- const body = (await resolved.json());
379
- if (!body.success || !body.data) {
380
- throw new Error(body.error ?? 'Existing manifest is invalid');
381
- }
382
- return {
383
- content: [{
384
- type: "text",
385
- text: JSON.stringify({
386
- status: 'existing',
387
- workspaceRoot,
388
- manifestPath: body.data.manifestPath,
389
- deck_id: body.data.manifest.deck_id,
390
- deck_name: body.data.deck.name,
391
- message: 'Workspace already linked. Session bound to this deck.',
392
- }, null, 2),
393
- }],
394
- };
395
- }
396
- if (!deckId) {
397
- const decks = await this.callBackendAPI('/api/decks');
398
- return {
399
- content: [{
400
- type: "text",
401
- text: JSON.stringify({
402
- status: 'needs_deck_id',
403
- workspaceRoot,
404
- expectedPath: shared_1.REPO_DECK_MANIFEST_PATH,
405
- message: 'Pick a deck id from the dashboard (My Decks → copy icon) and call setup_repo_deck again with deckId.',
406
- availableDecks: Array.isArray(decks)
407
- ? decks.map((d) => ({ id: d.id, name: d.name }))
408
- : decks,
409
- }, null, 2),
410
- }],
411
- };
412
- }
413
- const deck = await this.callBackendAPI(`/api/decks/${deckId}`);
414
- if (!deck?.id) {
415
- throw new Error(`Deck not found: ${deckId}`);
416
- }
417
- const content = (0, repo_deck_1.formatRepoDeckManifest)(deckId, deckName ?? deck.name);
418
- const dirPath = node_path_1.default.join(node_path_1.default.resolve(workspaceRoot), '.agent-deck');
419
- const filePath = (0, repo_deck_1.repoDeckManifestFilePath)(workspaceRoot);
420
- if (writeFile) {
421
- await promises_1.default.mkdir(dirPath, { recursive: true });
422
- await promises_1.default.writeFile(filePath, content, 'utf8');
423
- }
424
- this.sessionBinding.setWorkspace(sessionId, workspaceRoot);
425
- return {
426
- content: [{
427
- type: "text",
428
- text: JSON.stringify({
429
- status: writeFile ? 'created' : 'preview',
430
- workspaceRoot,
431
- filePath,
432
- deck_id: deckId,
433
- deck_name: deck.name,
434
- manifestContent: content,
435
- message: writeFile
436
- ? 'Created .agent-deck/deck.yaml and bound this session to the deck.'
437
- : 'Preview only — set writeFile true to write the file.',
438
- }, null, 2),
439
- }],
440
- };
441
- }
442
- catch (error) {
443
- return {
444
- content: [{ type: "text", text: JSON.stringify({ error: String(error) }, null, 2) }],
445
- };
446
- }
447
- });
448
297
  // Get all decks (metadata only; credentials stripped unless bound)
449
298
  this.registerTool("get_decks", {
450
299
  title: "Get Decks",
@@ -465,7 +314,7 @@ class AgentDeckMCPServer {
465
314
  });
466
315
  this.registerTool("get_bound_deck", {
467
316
  title: "Get Bound Deck",
468
- description: "Get the deck bound to this session (session deckId override, env, or .agent-deck/deck.yaml)",
317
+ description: "Get the deck bound to this session (session deckId or env AGENT_DECK_DECK_ID)",
469
318
  inputSchema: {},
470
319
  }, async () => {
471
320
  try {
@@ -1125,7 +974,7 @@ class AgentDeckMCPServer {
1125
974
  }
1126
975
  });
1127
976
  this.server.resource("bound_deck", "agent-deck://bound-deck", {
1128
- description: "Deck bound via .agent-deck/deck.yaml in the workspace",
977
+ description: "Deck bound to this MCP session",
1129
978
  mimeType: "application/json"
1130
979
  }, async () => {
1131
980
  try {
@@ -1244,8 +1093,9 @@ class AgentDeckMCPServer {
1244
1093
  async handleMcpPost(req, res) {
1245
1094
  const sessionIdHeader = this.getSessionIdHeader(req);
1246
1095
  const existing = sessionIdHeader ? this.sessions.get(sessionIdHeader) : undefined;
1247
- if (existing) {
1096
+ if (existing && sessionIdHeader) {
1248
1097
  this.activeSessionId = sessionIdHeader;
1098
+ this.touchLiveDisplay(sessionIdHeader);
1249
1099
  await existing.transport.handleRequest(req, res, req.body);
1250
1100
  return;
1251
1101
  }
@@ -1277,6 +1127,8 @@ class AgentDeckMCPServer {
1277
1127
  if (sessionId) {
1278
1128
  this.sessions.delete(sessionId);
1279
1129
  this.sessionBinding.clearSession(sessionId);
1130
+ this.badgeBySession.delete(sessionId);
1131
+ this.lastTouchAtMs.delete(sessionId);
1280
1132
  void this.unregisterLiveDisplay(sessionId);
1281
1133
  }
1282
1134
  };
@@ -1299,6 +1151,7 @@ class AgentDeckMCPServer {
1299
1151
  return;
1300
1152
  }
1301
1153
  this.activeSessionId = sessionId;
1154
+ this.touchLiveDisplay(sessionId);
1302
1155
  await session.transport.handleRequest(req, res);
1303
1156
  }
1304
1157
  async start() {
@@ -1308,12 +1161,10 @@ class AgentDeckMCPServer {
1308
1161
  this.app.listen(this.port, () => {
1309
1162
  console.log(`✅ Agent Deck MCP Server is ready to accept connections`);
1310
1163
  console.log(`📋 Available tools:`);
1311
- console.log(` - bind_workspace: Bind session to workspace; optional deckId override`);
1164
+ console.log(` - bind_workspace: Bind session to workspace + deck (deckId required)`);
1312
1165
  console.log(` - switch_bound_deck: Switch deck for this session only`);
1313
1166
  console.log(` - get_session_binding: Show session workspace + effective deck`);
1314
- console.log(` - get_repo_deck_status: Check repo deck manifest`);
1315
- console.log(` - setup_repo_deck: Create .agent-deck/deck.yaml for a repo`);
1316
- console.log(` - get_bound_deck: Get workspace-bound deck`);
1167
+ console.log(` - get_bound_deck: Get session-bound deck`);
1317
1168
  console.log(` - list_service_tools: List tools for a specific service`);
1318
1169
  console.log(` - call_service_tool: Call a tool on a service`);
1319
1170
  console.log(`📋 Available resources:`);