@ema.co/mcp-toolkit 2026.3.23-3 → 2026.3.24

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 (32) hide show
  1. package/LICENSE-COMMERCIAL.md +45 -0
  2. package/dist/config/generated/stage-eligibility.snapshot.js +118 -10
  3. package/dist/config/profile.js +4 -2
  4. package/dist/config/stage-resolution.js +2 -0
  5. package/dist/config/tool-guidance.js +6 -0
  6. package/dist/knowledge/gcp-auth.js +123 -17
  7. package/dist/knowledge/guidance-cache.js +24 -0
  8. package/dist/knowledge/search-client.js +95 -8
  9. package/dist/mcp/guidance.js +10 -0
  10. package/dist/mcp/handlers/config/index.js +12 -3
  11. package/dist/mcp/handlers/debug/formatters.js +1 -1
  12. package/dist/mcp/handlers/feedback/global-analysis-gcs.js +11 -3
  13. package/dist/mcp/handlers/feedback/index.js +15 -2
  14. package/dist/mcp/handlers/feedback/outbox.js +55 -0
  15. package/dist/mcp/handlers/index.js +2 -2
  16. package/dist/mcp/handlers/knowledge/index.js +11 -4
  17. package/dist/mcp/handlers/persona/create.js +14 -4
  18. package/dist/mcp/handlers/response-actions.js +38 -0
  19. package/dist/mcp/handlers/template/adapter.js +3 -3
  20. package/dist/mcp/handlers/template/index.js +14 -9
  21. package/dist/mcp/handlers/workflow/adapter.js +6 -4
  22. package/dist/mcp/handlers/workflow/deploy.js +1 -1
  23. package/dist/mcp/handlers/workflow/index.js +2 -2
  24. package/dist/mcp/handlers/workflow/optimize.js +1 -1
  25. package/dist/mcp/knowledge.js +2 -10
  26. package/dist/mcp/resources-dynamic.js +1 -1
  27. package/dist/mcp/server.js +16 -14
  28. package/dist/mcp/tools.js +7 -4
  29. package/dist/sdk/generated/agent-catalog.js +173 -0
  30. package/package.json +4 -2
  31. package/dist/mcp/handlers/catalog/index.js +0 -267
  32. package/dist/mcp/handlers/reference/index.js +0 -462
@@ -0,0 +1,45 @@
1
+ # Commercial License
2
+
3
+ Copyright (c) 2026 Ema Unlimited, Inc.
4
+
5
+ This software is available under a commercial license for organizations
6
+ that wish to use, modify, or distribute it without the obligations of
7
+ the GNU Affero General Public License (AGPL) v3.
8
+
9
+ ## Grant of License
10
+
11
+ Subject to payment of applicable fees and compliance with the terms
12
+ of a written commercial agreement, Ema Unlimited, Inc. grants you a
13
+ non-exclusive, non-transferable license to:
14
+
15
+ - Use the software in proprietary or closed-source environments
16
+ - Modify the software without public disclosure
17
+ - Deploy the software as part of a hosted or managed service
18
+ - Integrate the software into internal or commercial systems
19
+
20
+ ## Restrictions
21
+
22
+ Without an applicable commercial agreement, you may NOT:
23
+
24
+ - Offer the software as a hosted or managed service
25
+ - Expose the software’s network interfaces, handlers, or internal
26
+ port-based APIs in production
27
+ - Create derivative works that are not released under AGPLv3
28
+ - Circumvent licensing, feature controls, or access restrictions
29
+
30
+ ## Ownership
31
+
32
+ All rights, title, and interest in the software remain with
33
+ Ema Unlimited, Inc.. No rights are granted except as expressly stated.
34
+
35
+ ## Warranty Disclaimer
36
+
37
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
38
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY, FITNESS FOR A
39
+ PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
40
+
41
+ ## Contact
42
+
43
+ For licensing inquiries, please contact:
44
+
45
+ legal@ema.co or steven.poitras@ema.co
@@ -2,14 +2,14 @@
2
2
  * AUTO-GENERATED — Do not edit manually.
3
3
  * Run: npm run generate:stage-snapshot
4
4
  *
5
- * Generated from 45 catalog actions.
6
- * 28 stages, 45 actions with eligibility.
5
+ * Generated from 57 catalog actions.
6
+ * 29 stages, 57 actions with eligibility.
7
7
  * Overrides applied: abstain_action, custom_agent, document_synthesis, entity_extraction_with_documents, fixed_response, general_hitl, json_mapper, respond_for_external_actions, search, text_categorizer
8
8
  */
9
9
  export const STAGE_ELIGIBILITY_SNAPSHOT = {
10
- "catalogSize": 45,
11
- "stageCount": 28,
12
- "actionCount": 45,
10
+ "catalogSize": 57,
11
+ "stageCount": 29,
12
+ "actionCount": 57,
13
13
  "overridesApplied": [
14
14
  "abstain_action",
15
15
  "custom_agent",
@@ -32,7 +32,11 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
32
32
  "chat_categorizer",
33
33
  "conversation_to_search_query",
34
34
  "document_categorizer",
35
- "text_categorizer"
35
+ "feedback_categorizer",
36
+ "make_response_public",
37
+ "text_categorizer",
38
+ "thread_categorizer",
39
+ "thread_summarizer"
36
40
  ],
37
41
  "refine": [
38
42
  "chat_categorizer",
@@ -56,6 +60,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
56
60
  "information_redacter",
57
61
  "insurance_claim_summarizer",
58
62
  "legal_expert",
63
+ "market_research",
59
64
  "medical_record_summarizer",
60
65
  "medical_research_assistant",
61
66
  "phishing_email_detector",
@@ -81,6 +86,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
81
86
  ],
82
87
  "handle": [
83
88
  "call_llm",
89
+ "chart_creator",
84
90
  "custom_agent",
85
91
  "fixed_response",
86
92
  "generate_document",
@@ -90,11 +96,13 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
90
96
  "respond": [
91
97
  "audit_evidence_verifier",
92
98
  "call_llm",
99
+ "chart_creator",
93
100
  "compliance_document_analyzer",
94
101
  "custom_agent",
95
102
  "cybersecurity_expert",
96
103
  "document_synthesis",
97
104
  "email_writer",
105
+ "feedback_categorizer",
98
106
  "financial_risk_assessor",
99
107
  "financial_statement_analyzer",
100
108
  "fixed_response",
@@ -102,17 +110,23 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
102
110
  "information_redacter",
103
111
  "insurance_claim_summarizer",
104
112
  "legal_expert",
113
+ "make_response_public",
114
+ "market_research",
105
115
  "medical_record_summarizer",
106
116
  "medical_research_assistant",
107
117
  "phishing_email_detector",
118
+ "qa_over_sql",
108
119
  "respond_with_sources",
109
120
  "sales_intelligence",
110
121
  "sentiment_analyzer",
111
122
  "tax_advisor",
112
- "technical_support"
123
+ "technical_support",
124
+ "thread_categorizer",
125
+ "thread_summarizer"
113
126
  ],
114
127
  "reason": [
115
128
  "call_llm",
129
+ "chart_creator",
116
130
  "custom_agent",
117
131
  "fixed_response",
118
132
  "generate_document",
@@ -120,6 +134,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
120
134
  ],
121
135
  "draft": [
122
136
  "call_llm",
137
+ "chart_creator",
123
138
  "custom_agent",
124
139
  "fixed_response",
125
140
  "generate_document",
@@ -128,6 +143,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
128
143
  ],
129
144
  "compose": [
130
145
  "call_llm",
146
+ "chart_creator",
131
147
  "custom_agent",
132
148
  "fixed_response",
133
149
  "generate_document",
@@ -136,13 +152,19 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
136
152
  ],
137
153
  "summarize": [
138
154
  "call_llm",
155
+ "chart_creator",
139
156
  "custom_agent",
157
+ "feedback_categorizer",
140
158
  "fixed_response",
141
159
  "generate_document",
142
- "respond_with_sources"
160
+ "make_response_public",
161
+ "respond_with_sources",
162
+ "thread_categorizer",
163
+ "thread_summarizer"
143
164
  ],
144
165
  "resolve": [
145
166
  "call_llm",
167
+ "chart_creator",
146
168
  "custom_agent",
147
169
  "fixed_response",
148
170
  "generate_document",
@@ -152,6 +174,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
152
174
  "analyze": [
153
175
  "audit_evidence_verifier",
154
176
  "call_llm",
177
+ "chart_creator",
155
178
  "compliance_document_analyzer",
156
179
  "custom_agent",
157
180
  "financial_risk_assessor",
@@ -163,6 +186,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
163
186
  "legal_expert",
164
187
  "medical_record_summarizer",
165
188
  "medical_research_assistant",
189
+ "qa_over_sql",
166
190
  "respond_with_sources",
167
191
  "sentiment_analyzer",
168
192
  "tax_advisor"
@@ -170,6 +194,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
170
194
  "fallback": [
171
195
  "abstain_action",
172
196
  "call_llm",
197
+ "chart_creator",
173
198
  "custom_agent",
174
199
  "fixed_response",
175
200
  "generate_document",
@@ -177,6 +202,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
177
202
  ],
178
203
  "handle-failure": [
179
204
  "call_llm",
205
+ "chart_creator",
180
206
  "custom_agent",
181
207
  "fixed_response",
182
208
  "generate_document",
@@ -189,10 +215,15 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
189
215
  "general_hitl"
190
216
  ],
191
217
  "convert": [
218
+ "combine_search_results",
219
+ "combine_text_with_sources",
220
+ "convert_to_text",
221
+ "custom_code_agent",
192
222
  "json_extractor",
193
223
  "json_formatter",
194
224
  "json_mapper",
195
- "markdown_formatter"
225
+ "markdown_formatter",
226
+ "tags_extractor"
196
227
  ],
197
228
  "act-on-success": [
198
229
  "external_action_caller",
@@ -232,9 +263,21 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
232
263
  "rule_validation_with_documents"
233
264
  ],
234
265
  "format": [
266
+ "combine_search_results",
267
+ "combine_text_with_sources",
268
+ "convert_to_text",
269
+ "custom_code_agent",
235
270
  "json_extractor",
236
271
  "json_formatter",
237
- "markdown_formatter"
272
+ "markdown_formatter",
273
+ "tags_extractor"
274
+ ],
275
+ "merge": [
276
+ "combine_search_results",
277
+ "combine_text_with_sources",
278
+ "convert_to_text",
279
+ "custom_code_agent",
280
+ "tags_extractor"
238
281
  ]
239
282
  },
240
283
  "byAction": {
@@ -484,6 +527,71 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
484
527
  "markdown_formatter": [
485
528
  "convert",
486
529
  "format"
530
+ ],
531
+ "chart_creator": [
532
+ "analyze",
533
+ "compose",
534
+ "draft",
535
+ "fallback",
536
+ "handle",
537
+ "handle-failure",
538
+ "reason",
539
+ "resolve",
540
+ "respond",
541
+ "summarize"
542
+ ],
543
+ "combine_search_results": [
544
+ "convert",
545
+ "format",
546
+ "merge"
547
+ ],
548
+ "combine_text_with_sources": [
549
+ "convert",
550
+ "format",
551
+ "merge"
552
+ ],
553
+ "convert_to_text": [
554
+ "convert",
555
+ "format",
556
+ "merge"
557
+ ],
558
+ "custom_code_agent": [
559
+ "convert",
560
+ "format",
561
+ "merge"
562
+ ],
563
+ "tags_extractor": [
564
+ "convert",
565
+ "format",
566
+ "merge"
567
+ ],
568
+ "thread_categorizer": [
569
+ "classify",
570
+ "respond",
571
+ "summarize"
572
+ ],
573
+ "thread_summarizer": [
574
+ "classify",
575
+ "respond",
576
+ "summarize"
577
+ ],
578
+ "make_response_public": [
579
+ "classify",
580
+ "respond",
581
+ "summarize"
582
+ ],
583
+ "feedback_categorizer": [
584
+ "classify",
585
+ "respond",
586
+ "summarize"
587
+ ],
588
+ "market_research": [
589
+ "act",
590
+ "respond"
591
+ ],
592
+ "qa_over_sql": [
593
+ "analyze",
594
+ "respond"
487
595
  ]
488
596
  }
489
597
  };
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, writeFileSync, existsSync, renameSync, mkdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
- import { hasCachedToken } from "../knowledge/gcp-auth.js";
4
+ import { hasCachedToken, hasAdcCredentials } from "../knowledge/gcp-auth.js";
5
5
  // ─────────────────────────────────────────────────────────────────────────────
6
6
  // Helpers
7
7
  // ─────────────────────────────────────────────────────────────────────────────
@@ -287,10 +287,12 @@ export function getActiveProfile(dir) {
287
287
  ? new Date(envCred.gcpTokenExpiresAt).getTime() < Date.now()
288
288
  : false;
289
289
  const hasLoginGcpToken = !!envCred?.gcpToken && !gcpTokenExpired;
290
+ // ADC file check (primary method — auto-refreshable, no gcloud needed at runtime)
291
+ const hasAdc = hasAdcCredentials();
290
292
  // Check if a prior search already resolved a GCP token (any source).
291
293
  // Note: gcloud availability is checked at search time, not here (avoids blocking execSync).
292
294
  const hasResolvedAuth = hasCachedToken();
293
- const searchAvailable = hasGcpToken || hasGcpSaKey || hasDeApiKey || hasLoginGcpToken || hasResolvedAuth;
295
+ const searchAvailable = hasGcpToken || hasAdc || hasGcpSaKey || hasDeApiKey || hasLoginGcpToken || hasResolvedAuth;
294
296
  return {
295
297
  name: config.current_profile,
296
298
  display: displayName(profile.tenant.name, profile.environment.name, config),
@@ -34,6 +34,8 @@ const CATEGORY_STAGE_MAP = {
34
34
  it_security: ["act", "respond"],
35
35
  hr: ["act", "respond"],
36
36
  analytics: ["analyze", "respond"],
37
+ utilities: ["convert", "format", "merge"],
38
+ agent_assist: ["respond", "classify", "summarize"],
37
39
  };
38
40
  // ─────────────────────────────────────────────────────────────────────────────
39
41
  // Manual Overrides — semantic constraints not derivable from category alone
@@ -238,6 +238,11 @@ export const TOOL_GUIDANCE = {
238
238
  description: "Aggregate across ALL clients from cloud storage. Requires EMA_INTERNAL=1 and @ema.co gcloud auth.",
239
239
  example: 'feedback(method="analyze", scope="global")',
240
240
  },
241
+ {
242
+ name: "Read local messages",
243
+ description: "Read scoped messages from this session's in-memory buffer. For intra-session task coordination.",
244
+ example: 'feedback(method="local", scope="role:compliance")',
245
+ },
241
246
  {
242
247
  name: "Flush feedback",
243
248
  description: "Send locally accumulated feedback to cloud storage. Happens automatically, but can be triggered manually.",
@@ -247,6 +252,7 @@ export const TOOL_GUIDANCE = {
247
252
  nextSteps: {
248
253
  submit: "Continue with your task. Your feedback will be reviewed to improve the toolkit.",
249
254
  list: "Review entries and use analyze for aggregated insights.",
255
+ local: "Read messages scoped to you. Act on tasks, approvals, or coordination messages from other agents in this session.",
250
256
  analyze: "Address actionable_items by updating guidance, resources, or patterns documentation. Global analysis (scope='global') uses BigQuery for cross-client aggregation. Pass days=7 to narrow the analysis window. Response includes _backend indicator, trends (week-over-week with direction), and version_comparison (error rates by version for regression detection).",
251
257
  flush: "Feedback has been sent to cloud storage. Use analyze(scope='global') to see aggregated insights across all clients.",
252
258
  },
@@ -4,14 +4,20 @@
4
4
  * Resolution order (sync — getGcpAccessToken):
5
5
  * 1. EMA_GCP_ACCESS_TOKEN env var (explicit token)
6
6
  * 2. Cached token (from prior resolution)
7
- * 3. Stored OAuth credential from login flow (~/.ema-mcp/credentials.json)
8
- * 4. GOOGLE_APPLICATION_CREDENTIALS / EMA_GCP_SA_KEY (service account key → JWT → token)
9
- * 5. gcloud auth print-access-token (CLI fallback)
7
+ * 3. Application Default Credentials (~/.config/gcloud/application_default_credentials.json)
8
+ * 4. Stored OAuth credential from login flow (~/.ema-mcp/credentials.json)
9
+ * 5. GOOGLE_APPLICATION_CREDENTIALS / EMA_GCP_SA_KEY (service account key → JWT → token)
10
+ * 6. gcloud auth print-access-token (CLI fallback)
10
11
  *
11
12
  * Async variant (getGcpAccessTokenAsync) adds:
12
- * 6. Interactive: auto-launch Google auth if TTY available (once per session)
13
+ * 7. Interactive: auto-launch Google auth if TTY available (once per session)
13
14
  *
14
- * Service account auth (option 4) uses the standard Google OAuth2 JWT flow:
15
+ * ADC auth (option 3, PRIMARY) uses the OAuth2 refresh_token grant:
16
+ * ADC file → refresh_token → POST oauth2.googleapis.com/token → access_token
17
+ * Auto-refreshes without user interaction. Set up once with:
18
+ * gcloud auth application-default login
19
+ *
20
+ * Service account auth (option 5) uses the standard Google OAuth2 JWT flow:
15
21
  * SA key → sign JWT → POST oauth2.googleapis.com/token → access_token
16
22
  * No external dependencies needed (uses Node.js built-in crypto).
17
23
  */
@@ -127,6 +133,77 @@ async function getTokenFromServiceAccount(sa) {
127
133
  return undefined;
128
134
  }
129
135
  }
136
+ /**
137
+ * Load ADC from the well-known file location.
138
+ * Written by `gcloud auth application-default login`.
139
+ *
140
+ * Only returns `authorized_user` type credentials (with refresh_token).
141
+ * Service account type is already handled by loadServiceAccountKey().
142
+ */
143
+ function loadAdcCredentials() {
144
+ const debug = !!process.env.EMA_DEBUG;
145
+ try {
146
+ // Well-known path: ~/.config/gcloud/application_default_credentials.json
147
+ // On Windows: %APPDATA%\gcloud\application_default_credentials.json
148
+ const adcPath = process.platform === "win32"
149
+ ? join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "gcloud", "application_default_credentials.json")
150
+ : join(homedir(), ".config", "gcloud", "application_default_credentials.json");
151
+ const raw = readFileSync(adcPath, "utf-8");
152
+ const creds = JSON.parse(raw);
153
+ if (creds.type !== "authorized_user") {
154
+ if (debug)
155
+ console.error(`[GCP-AUTH] ADC file is type "${creds.type}", not authorized_user — skipping.`);
156
+ return undefined;
157
+ }
158
+ if (!creds.client_id || !creds.client_secret || !creds.refresh_token) {
159
+ if (debug)
160
+ console.error("[GCP-AUTH] ADC file missing required fields (client_id, client_secret, refresh_token).");
161
+ return undefined;
162
+ }
163
+ if (debug)
164
+ console.error("[GCP-AUTH] Found ADC authorized_user credentials.");
165
+ return creds;
166
+ }
167
+ catch {
168
+ // File doesn't exist or isn't readable — that's fine
169
+ return undefined;
170
+ }
171
+ }
172
+ /**
173
+ * Exchange an ADC refresh token for a fresh access token.
174
+ * Uses the standard OAuth2 refresh_token grant.
175
+ */
176
+ async function getTokenFromAdc(adc) {
177
+ const debug = !!process.env.EMA_DEBUG;
178
+ try {
179
+ const resp = await fetch("https://oauth2.googleapis.com/token", {
180
+ method: "POST",
181
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
182
+ body: new URLSearchParams({
183
+ client_id: adc.client_id,
184
+ client_secret: adc.client_secret,
185
+ refresh_token: adc.refresh_token,
186
+ grant_type: "refresh_token",
187
+ }).toString(),
188
+ signal: AbortSignal.timeout(10_000),
189
+ });
190
+ if (!resp.ok) {
191
+ const detail = await resp.text().catch(() => "");
192
+ if (debug)
193
+ console.error(`[GCP-AUTH] ADC token refresh failed: ${resp.status} — ${detail.slice(0, 200)}`);
194
+ return undefined;
195
+ }
196
+ const data = (await resp.json());
197
+ if (debug)
198
+ console.error(`[GCP-AUTH] ADC token refreshed (expires in ${data.expires_in ?? "?"}s).`);
199
+ return data.access_token;
200
+ }
201
+ catch (err) {
202
+ if (debug)
203
+ console.error(`[GCP-AUTH] ADC token refresh error: ${err instanceof Error ? err.message : String(err)}`);
204
+ return undefined;
205
+ }
206
+ }
130
207
  // ─────────────────────────────────────────────────────────────────────────────
131
208
  // Main token getter
132
209
  // ─────────────────────────────────────────────────────────────────────────────
@@ -136,9 +213,10 @@ async function getTokenFromServiceAccount(sa) {
136
213
  * Resolution order:
137
214
  * 1. EMA_GCP_ACCESS_TOKEN env var — skipped if previously rejected (401/403)
138
215
  * 2. Cached token (if not expired)
139
- * 3. Stored OAuth credential from login flow (~/.ema-mcp/credentials.json)
140
- * 4. Service account key (GOOGLE_APPLICATION_CREDENTIALS or EMA_GCP_SA_KEY)
141
- * 5. gcloud auth print-access-token (CLI fallback)
216
+ * 3. ADC file (~/.config/gcloud/application_default_credentials.json) — primary
217
+ * 4. Stored OAuth credential from login flow (~/.ema-mcp/credentials.json)
218
+ * 5. Service account key (GOOGLE_APPLICATION_CREDENTIALS or EMA_GCP_SA_KEY)
219
+ * 6. gcloud auth print-access-token (CLI fallback)
142
220
  *
143
221
  * Returns undefined if no token source is available.
144
222
  */
@@ -157,13 +235,28 @@ export function getGcpAccessToken() {
157
235
  console.error(`[GCP-AUTH] Using cached token (expires in ${Math.round((_token.expiresAt - Date.now()) / 60_000)}m).`);
158
236
  return _token.value;
159
237
  }
160
- // 3. Stored OAuth credential from login flow
238
+ // 3. ADC file async refresh token exchange (PRIMARY)
239
+ // Can't do async in a sync function, so fire-and-forget + cache for next call
240
+ const adc = loadAdcCredentials();
241
+ if (adc) {
242
+ if (debug)
243
+ console.error("[GCP-AUTH] Found ADC credentials — initiating async token refresh.");
244
+ getTokenFromAdc(adc).then((token) => {
245
+ if (token) {
246
+ _token = { value: token, expiresAt: Date.now() + TOKEN_TTL_MS };
247
+ }
248
+ }).catch(() => { });
249
+ // If we have a cached token from a prior ADC refresh, use it
250
+ if (_token && _token.value)
251
+ return _token.value;
252
+ }
253
+ // 4. Stored OAuth credential from login flow
161
254
  const storedToken = loadStoredGcpToken();
162
255
  if (storedToken) {
163
256
  _token = { value: storedToken, expiresAt: Date.now() + TOKEN_TTL_MS };
164
257
  return storedToken;
165
258
  }
166
- // 4. Service account key → async token exchange
259
+ // 5. Service account key → async token exchange
167
260
  // Can't do async in a sync function, so we kick off the exchange and cache for next call
168
261
  const sa = loadServiceAccountKey();
169
262
  if (sa) {
@@ -179,7 +272,7 @@ export function getGcpAccessToken() {
179
272
  if (_token && _token.value)
180
273
  return _token.value;
181
274
  }
182
- // 5. gcloud CLI
275
+ // 6. gcloud CLI
183
276
  try {
184
277
  if (debug)
185
278
  console.error("[GCP-AUTH] Trying gcloud auth print-access-token...");
@@ -206,11 +299,11 @@ export function getGcpAccessToken() {
206
299
  }
207
300
  }
208
301
  if (debug)
209
- console.error("[GCP-AUTH] All 5 resolution steps failed. No GCP token available.");
302
+ console.error("[GCP-AUTH] All 6 resolution steps failed. No GCP token available.");
210
303
  return undefined;
211
304
  }
212
305
  /**
213
- * Async version — waits for service account token exchange.
306
+ * Async version — waits for ADC/SA token exchange.
214
307
  * Use this when you can await (e.g., in search handlers).
215
308
  */
216
309
  export async function getGcpAccessTokenAsync() {
@@ -221,13 +314,22 @@ export async function getGcpAccessTokenAsync() {
221
314
  // 2. Cached token
222
315
  if (_token && Date.now() < _token.expiresAt)
223
316
  return _token.value;
224
- // 3. Stored OAuth credential from login flow
317
+ // 3. ADC file await refresh token exchange (PRIMARY)
318
+ const adc = loadAdcCredentials();
319
+ if (adc) {
320
+ const token = await getTokenFromAdc(adc);
321
+ if (token) {
322
+ _token = { value: token, expiresAt: Date.now() + TOKEN_TTL_MS };
323
+ return token;
324
+ }
325
+ }
326
+ // 4. Stored OAuth credential from login flow
225
327
  const storedToken = loadStoredGcpToken();
226
328
  if (storedToken) {
227
329
  _token = { value: storedToken, expiresAt: Date.now() + TOKEN_TTL_MS };
228
330
  return storedToken;
229
331
  }
230
- // 4. Service account key → await token exchange
332
+ // 5. Service account key → await token exchange
231
333
  const sa = loadServiceAccountKey();
232
334
  if (sa) {
233
335
  const token = await getTokenFromServiceAccount(sa);
@@ -236,11 +338,11 @@ export async function getGcpAccessTokenAsync() {
236
338
  return token;
237
339
  }
238
340
  }
239
- // 5. gcloud CLI (sync — handled inside getGcpAccessToken)
341
+ // 6. gcloud CLI (sync — handled inside getGcpAccessToken)
240
342
  const syncToken = getGcpAccessToken();
241
343
  if (syncToken)
242
344
  return syncToken;
243
- // 6. Interactive: auto-launch Google auth if TTY available
345
+ // 7. Interactive: auto-launch Google auth if TTY available
244
346
  const refreshed = await refreshGcpToken();
245
347
  if (refreshed)
246
348
  return refreshed;
@@ -374,6 +476,10 @@ export function getLastGcpAuthError() {
374
476
  export function hasCachedToken() {
375
477
  return !!_token && Date.now() < _token.expiresAt;
376
478
  }
479
+ /** Whether ADC file exists with authorized_user credentials (fast sync check, no network). */
480
+ export function hasAdcCredentials() {
481
+ return !!loadAdcCredentials();
482
+ }
377
483
  let _gcloudAvailable;
378
484
  /**
379
485
  * Whether `gcloud` CLI is installed (cached after first check).
@@ -27,6 +27,8 @@ export class GuidanceCache {
27
27
  toolGuidanceMap = new Map();
28
28
  tips = [];
29
29
  topics = [];
30
+ patterns = [];
31
+ questions = [];
30
32
  warmedAt = null;
31
33
  constructor(searchFn, config) {
32
34
  this.searchFn = searchFn;
@@ -102,12 +104,24 @@ export class GuidanceCache {
102
104
  this.maybeRefresh();
103
105
  return this.topics;
104
106
  }
107
+ /** All workflow patterns. */
108
+ getPatterns() {
109
+ this.maybeRefresh();
110
+ return this.patterns;
111
+ }
112
+ /** All qualifying questions. */
113
+ getQuestions() {
114
+ this.maybeRefresh();
115
+ return this.questions;
116
+ }
105
117
  // ── Internals ───────────────────────────────────────────────────────────
106
118
  clear() {
107
119
  this.rules = [];
108
120
  this.toolGuidanceMap = new Map();
109
121
  this.tips = [];
110
122
  this.topics = [];
123
+ this.patterns = [];
124
+ this.questions = [];
111
125
  }
112
126
  /**
113
127
  * Index artifacts by reading structured data from structData.data.
@@ -152,6 +166,12 @@ export class GuidanceCache {
152
166
  case "guidance-topic":
153
167
  this.topics.push(data);
154
168
  break;
169
+ case "workflow-pattern":
170
+ this.patterns.push(data);
171
+ break;
172
+ case "qualifying-question":
173
+ this.questions.push(data);
174
+ break;
155
175
  default:
156
176
  break;
157
177
  }
@@ -171,6 +191,10 @@ export class GuidanceCache {
171
191
  return "contextual-tip";
172
192
  if (tags.includes("guidance-topic"))
173
193
  return "guidance-topic";
194
+ if (tags.includes("workflow-pattern"))
195
+ return "workflow-pattern";
196
+ if (tags.includes("qualifying-question"))
197
+ return "qualifying-question";
174
198
  if (name.includes("tool-guidance"))
175
199
  return "tool-guidance";
176
200
  return undefined;