ummaya 0.2.2 → 0.2.4

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 (111) hide show
  1. package/README.md +2 -1
  2. package/npm-shrinkwrap.json +2 -2
  3. package/package.json +1 -1
  4. package/prompts/manifest.yaml +2 -2
  5. package/prompts/session_guidance_v1.md +3 -1
  6. package/prompts/system_v1.md +8 -7
  7. package/pyproject.toml +2 -7
  8. package/src/ummaya/context/builder.py +17 -11
  9. package/src/ummaya/engine/engine.py +27 -7
  10. package/src/ummaya/engine/query.py +20 -0
  11. package/src/ummaya/evidence/__init__.py +25 -0
  12. package/src/ummaya/evidence/__main__.py +7 -0
  13. package/src/ummaya/evidence/models.py +58 -0
  14. package/src/ummaya/evidence/runner.py +308 -0
  15. package/src/ummaya/evidence/task_registry.py +264 -0
  16. package/src/ummaya/ipc/frame_schema.py +47 -0
  17. package/src/ummaya/ipc/stdio.py +1349 -90
  18. package/src/ummaya/llm/client.py +132 -56
  19. package/src/ummaya/llm/reasoning.py +84 -0
  20. package/src/ummaya/tools/discovery_bridge.py +17 -1
  21. package/src/ummaya/tools/executor.py +32 -12
  22. package/src/ummaya/tools/geocoding/kakao_client.py +1 -2
  23. package/src/ummaya/tools/kma/apihub_catalog.py +984 -1
  24. package/src/ummaya/tools/kma/apihub_structured_adapter.py +86 -6
  25. package/src/ummaya/tools/kma/apihub_url_adapter.py +593 -0
  26. package/src/ummaya/tools/kma/apihub_url_catalog.py +296 -0
  27. package/src/ummaya/tools/location_adapters.py +8 -6
  28. package/src/ummaya/tools/manifest_metadata.py +16 -3
  29. package/src/ummaya/tools/mvp_surface.py +2 -2
  30. package/src/ummaya/tools/nmc/emergency_search.py +8 -6
  31. package/src/ummaya/tools/register_all.py +9 -0
  32. package/src/ummaya/tools/resolve_location.py +4 -4
  33. package/src/ummaya/tools/search.py +664 -18
  34. package/src/ummaya/tools/verified_data_go_kr/_manifest.py +115 -25
  35. package/src/ummaya/tools/verified_data_go_kr/airkorea_air_quality.py +109 -4
  36. package/src/ummaya/tools/verified_data_go_kr/nmc_aed_site.py +108 -2
  37. package/src/ummaya/tools/verified_data_go_kr/pps_bid_public_info.py +174 -9
  38. package/src/ummaya/tools/verified_data_go_kr/tago_bus_arrival.py +66 -3
  39. package/src/ummaya/tools/verified_data_go_kr/tago_bus_location.py +12 -2
  40. package/src/ummaya/tools/verified_data_go_kr/tago_bus_route.py +8 -2
  41. package/src/ummaya/tools/verified_data_go_kr/tago_bus_route_station.py +114 -0
  42. package/src/ummaya/tools/verified_data_go_kr/tago_bus_station.py +14 -3
  43. package/src/ummaya/tools/verify_canonical_map.py +21 -0
  44. package/tui/package.json +1 -2
  45. package/tui/src/QueryEngine.ts +4 -0
  46. package/tui/src/cli/handlers/auth.ts +1 -1
  47. package/tui/src/cli/handlers/mcp.tsx +3 -3
  48. package/tui/src/cli/print.ts +69 -18
  49. package/tui/src/cli/update.ts +13 -13
  50. package/tui/src/commands/copy/index.ts +1 -1
  51. package/tui/src/commands/cost/cost.ts +2 -2
  52. package/tui/src/commands/init-verifiers.ts +5 -5
  53. package/tui/src/commands/init.ts +30 -30
  54. package/tui/src/commands/insights.ts +43 -43
  55. package/tui/src/commands/install-github-app/install-github-app.tsx +2 -2
  56. package/tui/src/commands/install-github-app/setupGitHubActions.ts +3 -3
  57. package/tui/src/commands/install.tsx +5 -5
  58. package/tui/src/commands/mcp/addCommand.ts +5 -5
  59. package/tui/src/commands/mcp/xaaIdpCommand.ts +2 -2
  60. package/tui/src/commands/plugin/ManageMarketplaces.tsx +2 -2
  61. package/tui/src/commands/reasoning/index.ts +13 -0
  62. package/tui/src/commands/reasoning/reasoning.tsx +177 -0
  63. package/tui/src/commands/thinkback/thinkback.tsx +3 -3
  64. package/tui/src/commands.ts +2 -0
  65. package/tui/src/components/Messages.tsx +2 -1
  66. package/tui/src/components/Spinner.tsx +2 -2
  67. package/tui/src/components/design-system/LoadingState.tsx +2 -2
  68. package/tui/src/ipc/codec.ts +26 -0
  69. package/tui/src/ipc/frames.generated.ts +398 -303
  70. package/tui/src/ipc/llmClient.ts +130 -51
  71. package/tui/src/ipc/llmTypes.ts +16 -1
  72. package/tui/src/ipc/schema/frame.schema.json +1 -3475
  73. package/tui/src/main.tsx +3 -0
  74. package/tui/src/query.ts +467 -2
  75. package/tui/src/screens/REPL.tsx +3 -3
  76. package/tui/src/services/api/claude.ts +54 -25
  77. package/tui/src/services/api/client.ts +33 -12
  78. package/tui/src/services/api/ummaya.ts +70 -16
  79. package/tui/src/skills/bundled/stuck.ts +12 -12
  80. package/tui/src/state/AppStateStore.ts +7 -0
  81. package/tui/src/tools/AdapterTool/AdapterTool.ts +590 -7
  82. package/tui/src/tools/LookupPrimitive/LookupPrimitive.ts +43 -17
  83. package/tui/src/tools/LookupPrimitive/prompt.ts +7 -6
  84. package/tui/src/tools/ResolveLocationPrimitive/ResolveLocationPrimitive.ts +40 -19
  85. package/tui/src/tools/SubmitPrimitive/SubmitPrimitive.ts +25 -9
  86. package/tui/src/tools/VerifyPrimitive/VerifyPrimitive.ts +25 -9
  87. package/tui/src/tools/_shared/citizenUserText.ts +49 -0
  88. package/tui/src/tools/_shared/directPublicDataGuard.ts +362 -0
  89. package/tui/src/tools/_shared/kmaAnalysisGuard.ts +197 -0
  90. package/tui/src/tools/_shared/kmaAviationGuard.ts +70 -0
  91. package/tui/src/tools/_shared/locationInputRepair.ts +112 -0
  92. package/tui/src/tools/_shared/nmcAedGuard.ts +234 -0
  93. package/tui/src/tools/_shared/protectedCheckGuard.ts +207 -0
  94. package/tui/src/tools/_shared/rootPrimitiveInput.ts +67 -0
  95. package/tui/src/tools/_shared/textToolCallGuard.ts +91 -0
  96. package/tui/src/tools/_shared/toolChoiceRepair.ts +866 -0
  97. package/tui/src/utils/attachments.ts +1 -1
  98. package/tui/src/utils/kExaoneReasoning.ts +138 -0
  99. package/tui/src/utils/messages.ts +1 -0
  100. package/tui/src/utils/multiToolLayout.ts +13 -0
  101. package/tui/src/utils/processUserInput/processSlashCommand.tsx +2 -2
  102. package/tui/src/utils/processUserInput/processUserInput.ts +26 -0
  103. package/tui/src/utils/settings/applySettingsChange.ts +4 -0
  104. package/tui/src/utils/settings/types.ts +9 -3
  105. package/tui/src/utils/stats.ts +1 -1
  106. package/uv.lock +1 -15
  107. package/assets/copilot-gate-logo.svg +0 -58
  108. package/assets/govon-logo.svg +0 -40
  109. package/src/ummaya/eval/__init__.py +0 -5
  110. package/src/ummaya/eval/retrieval.py +0 -713
  111. package/tui/src/utils/messageStream.ts +0 -186
@@ -28,6 +28,11 @@ import {
28
28
  } from '../../services/api/adapterManifest.js'
29
29
  import { FIND_TOOL_NAME, DESCRIPTION, FIND_TOOL_PROMPT } from './prompt.js'
30
30
  import { dispatchPrimitive } from '../_shared/dispatchPrimitive.js'
31
+ import { validateKmaAviationToolChoice } from '../_shared/kmaAviationGuard.js'
32
+ import { validateKmaAnalysisToolChoice } from '../_shared/kmaAnalysisGuard.js'
33
+ import { validateNmcAedToolChoice } from '../_shared/nmcAedGuard.js'
34
+ import { validateProtectedCheckToolChoice } from '../_shared/protectedCheckGuard.js'
35
+ import { validateDirectPublicDataToolChoice } from '../_shared/directPublicDataGuard.js'
31
36
  import {
32
37
  renderVerboseInputJson,
33
38
  renderVerboseOutputJson,
@@ -38,6 +43,11 @@ import {
38
43
  } from '../_shared/compactPrimitiveResult.js'
39
44
  import { getOrCreateUmmayaBridge } from '../../ipc/bridgeSingleton.js'
40
45
  import { getOrCreatePendingCallRegistry } from '../../ipc/pendingCallSingleton.js'
46
+ import {
47
+ isRootPrimitiveToolId,
48
+ normalizeRootPrimitiveAdapterEnvelope,
49
+ rootPrimitiveSelfTargetMessage,
50
+ } from '../_shared/rootPrimitiveInput.js'
41
51
 
42
52
  // ---------------------------------------------------------------------------
43
53
  // UMMAYA citation extension — attaches resolved citation to the context so the
@@ -48,8 +58,6 @@ type ContextWithCitation = ToolUseContext & {
48
58
  ummayaCitations?: AdapterCitation[]
49
59
  }
50
60
 
51
- const ROOT_PRIMITIVE_TOOL_IDS = new Set(['find', 'locate', 'check', 'send'])
52
-
53
61
  function asRecord(value: unknown): Record<string, unknown> | null {
54
62
  return value !== null && typeof value === 'object'
55
63
  ? value as Record<string, unknown>
@@ -531,19 +539,22 @@ function buildFindResultRows(output: Output): React.ReactNode[] {
531
539
  // ---------------------------------------------------------------------------
532
540
 
533
541
  const inputSchema = lazySchema(() =>
534
- z.object({
535
- tool_id: z
536
- .string()
537
- .min(1)
538
- .describe(
539
- 'Concrete adapter identifier picked from <available_adapters>. ' +
540
- 'This is not the function name. Never use "find", "locate", "check", or "send". ' +
541
- 'Examples: "kma_forecast_fetch", "hira_hospital_search".',
542
- ),
543
- params: z
544
- .record(z.string(), z.unknown())
545
- .describe('Adapter-defined Pydantic-validated parameter body'),
546
- }),
542
+ z.preprocess(
543
+ value => normalizeRootPrimitiveAdapterEnvelope(FIND_TOOL_NAME, value),
544
+ z.object({
545
+ tool_id: z
546
+ .string()
547
+ .min(1)
548
+ .describe(
549
+ 'Concrete adapter identifier picked from <available_adapters>. ' +
550
+ 'This is not the function name. Never use "find", "locate", "check", or "send". ' +
551
+ 'Examples: "kma_forecast_fetch", "hira_hospital_search".',
552
+ ),
553
+ params: z
554
+ .record(z.string(), z.unknown())
555
+ .describe('Adapter-defined Pydantic-validated parameter body'),
556
+ }),
557
+ ),
547
558
  )
548
559
  type InputSchema = ReturnType<typeof inputSchema>
549
560
 
@@ -678,14 +689,29 @@ export const LookupPrimitive = buildTool({
678
689
  // policy URL) is enforced at the primitive surface, not just at the
679
690
  // permission gauntlet.
680
691
 
681
- if (ROOT_PRIMITIVE_TOOL_IDS.has(input.tool_id)) {
692
+ if (isRootPrimitiveToolId(input.tool_id)) {
682
693
  return {
683
694
  result: false,
684
- message: `Root primitive '${input.tool_id}' is not an adapter tool_id. Pick a concrete adapter from <available_adapters>.`,
695
+ message: rootPrimitiveSelfTargetMessage(input.tool_id, 'find'),
685
696
  errorCode: PrimitiveErrorCode.AdapterNotFound,
686
697
  }
687
698
  }
688
699
 
700
+ const protectedChoice = validateProtectedCheckToolChoice(input.tool_id, context)
701
+ if (protectedChoice) return protectedChoice
702
+ const directPublicDataChoice = validateDirectPublicDataToolChoice(
703
+ input.tool_id,
704
+ context,
705
+ input.params,
706
+ )
707
+ if (directPublicDataChoice) return directPublicDataChoice
708
+ const kmaAviationChoice = validateKmaAviationToolChoice(input.tool_id, context)
709
+ if (kmaAviationChoice) return kmaAviationChoice
710
+ const kmaAnalysisChoice = validateKmaAnalysisToolChoice(input.tool_id, context)
711
+ if (kmaAnalysisChoice) return kmaAnalysisChoice
712
+ const nmcAedChoice = validateNmcAedToolChoice(input.tool_id, context)
713
+ if (nmcAedChoice) return nmcAedChoice
714
+
689
715
  // Tier 1 — synced backend manifest (FR-017).
690
716
  if (isManifestSynced()) {
691
717
  const backendEntry = resolveAdapter(input.tool_id)
@@ -1,16 +1,16 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // UMMAYA-original — Epic #1634 P3 · FindPrimitive prompt strings.
3
3
  // 2026 migration note: root primitives are lightweight category descriptors
4
- // and legacy transcript compatibility wrappers. Concrete adapter functions are
5
- // loaded through CC ToolSearch/deferred schema expansion or backend top-K
6
- // retrieval, then called directly with adapter schema arguments.
4
+ // and legacy transcript compatibility wrappers. Concrete adapter ids are
5
+ // selected through CC ToolSearch/deferred schema expansion or backend top-K
6
+ // retrieval, then exposed as concrete model-facing tool functions.
7
7
  // Contract: specs/1634-tool-system-wiring/contracts/primitive-envelope.md § 2
8
8
 
9
9
  export const FIND_TOOL_NAME = 'find'
10
10
 
11
11
  /** Citizen-facing English description shown to the LLM (<= 240 chars). */
12
12
  export const DESCRIPTION =
13
- 'Discover Korean public-service lookup adapters. Prefer concrete adapter functions loaded by ToolSearch or backend retrieval; find is a legacy wrapper only.'
13
+ 'Discover Korean public-service lookup adapters. Prefer concrete lookup adapter functions loaded by ToolSearch or backend retrieval; find is a legacy wrapper.'
14
14
 
15
15
  /** Extended prompt included in the system-prompt tool-use section. */
16
16
  export const FIND_TOOL_PROMPT = `Discover Korean public-service lookup adapters registered in the UMMAYA tool registry.
@@ -18,14 +18,15 @@ export const FIND_TOOL_PROMPT = `Discover Korean public-service lookup adapters
18
18
  Preferred path:
19
19
  - Call concrete adapter functions directly after their schemas are loaded.
20
20
  - Example: kma_current_observation({ base_date: "YYYYMMDD", base_time: "HH00", nx: 97, ny: 74 })
21
- - Adapter schemas are progressively disclosed by ToolSearch or by backend top-K retrieval for the current citizen request.
21
+ - Adapter ids and schemas are progressively disclosed by ToolSearch, adapter_manifest, or backend top-K retrieval for the current citizen request.
22
22
  - Only top candidates should be loaded; do not expect every adapter schema in the prompt.
23
+ - A concrete adapter id may be valid even when it is not listed in <available-deferred-tools>; if it appears in <available_adapters> or adapter_manifest and its function is loaded, call that function directly with its schema fields.
23
24
 
24
25
  Legacy root wrapper:
25
26
  - If a concrete adapter function is not loaded and only the root primitive is available, find accepts { tool_id, params } for old transcripts and compatibility paths.
26
27
  - tool_id must be a concrete adapter id from <available_adapters>, never "find", "locate", "check", or "send".
27
28
  - Invalid: find({ tool_id: "find", params: {...} })
28
- - Compatibility-only: find({ tool_id: "kma_current_observation", params: { base_date: "YYYYMMDD", base_time: "HH00", nx: 97, ny: 74 } })
29
+ - Compatibility-only: find({ tool_id: "kma_apihub_url_air_metar_decoded", params: { org: "K", help: 1 } })
29
30
 
30
31
  Rules:
31
32
  - Do not call find with mode='search' or query; discovery is handled outside the primitive call.
@@ -8,7 +8,7 @@ import React from 'react'
8
8
  import { z } from 'zod/v4'
9
9
  import { Text } from '../../ink.js'
10
10
  import { MessageResponse } from '../../components/MessageResponse.js'
11
- import { buildTool, type ToolDef } from '../../Tool.js'
11
+ import { buildTool, type ToolDef, type ToolUseContext } from '../../Tool.js'
12
12
  import { lazySchema } from '../../utils/lazySchema.js'
13
13
  import {
14
14
  LOCATE_TOOL_NAME,
@@ -16,6 +16,8 @@ import {
16
16
  LOCATE_TOOL_PROMPT,
17
17
  } from './prompt.js'
18
18
  import { dispatchPrimitive } from '../_shared/dispatchPrimitive.js'
19
+ import { validateKmaAviationToolChoice } from '../_shared/kmaAviationGuard.js'
20
+ import { validateDirectPublicDataToolChoice } from '../_shared/directPublicDataGuard.js'
19
21
  import {
20
22
  renderVerboseInputJson,
21
23
  renderVerboseOutputJson,
@@ -26,22 +28,29 @@ import {
26
28
  } from '../_shared/compactPrimitiveResult.js'
27
29
  import { getOrCreateUmmayaBridge } from '../../ipc/bridgeSingleton.js'
28
30
  import { getOrCreatePendingCallRegistry } from '../../ipc/pendingCallSingleton.js'
29
-
30
- const ROOT_PRIMITIVE_TOOL_IDS = new Set(['find', 'locate', 'check', 'send'])
31
+ import {
32
+ isRootPrimitiveToolId,
33
+ normalizeRootPrimitiveAdapterEnvelope,
34
+ rootPrimitiveSelfTargetMessage,
35
+ } from '../_shared/rootPrimitiveInput.js'
36
+ import { repairLocateQueryParamsFromConversation } from '../_shared/locationInputRepair.js'
31
37
 
32
38
  const inputSchema = lazySchema(() =>
33
- z.strictObject({
34
- tool_id: z
35
- .string()
36
- .min(1)
37
- .describe(
38
- 'Concrete locate adapter identifier from <available_adapters>. ' +
39
- 'This is not the function name. Never use "locate", "find", "check", or "send".',
40
- ),
41
- params: z
42
- .record(z.string(), z.unknown())
43
- .describe('Adapter-defined Pydantic-validated parameter body.'),
44
- }),
39
+ z.preprocess(
40
+ value => normalizeRootPrimitiveAdapterEnvelope(LOCATE_TOOL_NAME, value),
41
+ z.strictObject({
42
+ tool_id: z
43
+ .string()
44
+ .min(1)
45
+ .describe(
46
+ 'Concrete locate adapter identifier from <available_adapters>. ' +
47
+ 'This is not the function name. Never use "locate", "find", "check", or "send".',
48
+ ),
49
+ params: z
50
+ .record(z.string(), z.unknown())
51
+ .describe('Adapter-defined Pydantic-validated parameter body.'),
52
+ }),
53
+ ),
45
54
  )
46
55
  type InputSchema = ReturnType<typeof inputSchema>
47
56
 
@@ -260,14 +269,22 @@ export const ResolveLocationPrimitive = buildTool({
260
269
 
261
270
  isMcp: false,
262
271
 
263
- async validateInput(input: z.infer<InputSchema>) {
264
- if (ROOT_PRIMITIVE_TOOL_IDS.has(input.tool_id)) {
272
+ async validateInput(input: z.infer<InputSchema>, context: ToolUseContext) {
273
+ if (isRootPrimitiveToolId(input.tool_id)) {
265
274
  return {
266
275
  result: false as const,
267
- message: `Root primitive '${input.tool_id}' is not a locate adapter tool_id. Pick a concrete locate adapter from <available_adapters>.`,
276
+ message: rootPrimitiveSelfTargetMessage(input.tool_id, 'locate'),
268
277
  errorCode: 1,
269
278
  }
270
279
  }
280
+ const kmaAviationChoice = validateKmaAviationToolChoice(input.tool_id, context)
281
+ if (kmaAviationChoice) return kmaAviationChoice
282
+ const directPublicDataChoice = validateDirectPublicDataToolChoice(
283
+ input.tool_id,
284
+ context,
285
+ input.params,
286
+ )
287
+ if (directPublicDataChoice) return directPublicDataChoice
271
288
  return { result: true as const }
272
289
  },
273
290
 
@@ -307,9 +324,13 @@ export const ResolveLocationPrimitive = buildTool({
307
324
  },
308
325
 
309
326
  async call(input, context) {
327
+ const repairedInput = repairLocateQueryParamsFromConversation(
328
+ input as Record<string, unknown>,
329
+ context.messages,
330
+ )
310
331
  return dispatchPrimitive<Output>({
311
332
  primitive: 'locate',
312
- args: input as Record<string, unknown>,
333
+ args: repairedInput,
313
334
  context,
314
335
  registry: getOrCreatePendingCallRegistry(),
315
336
  bridge: getOrCreateUmmayaBridge(),
@@ -37,6 +37,11 @@ import {
37
37
  } from '../_shared/compactPrimitiveResult.js'
38
38
  import { getOrCreateUmmayaBridge } from '../../ipc/bridgeSingleton.js'
39
39
  import { getOrCreatePendingCallRegistry } from '../../ipc/pendingCallSingleton.js'
40
+ import {
41
+ isRootPrimitiveToolId,
42
+ normalizeRootPrimitiveAdapterEnvelope,
43
+ rootPrimitiveSelfTargetMessage,
44
+ } from '../_shared/rootPrimitiveInput.js'
40
45
 
41
46
  // ---------------------------------------------------------------------------
42
47
  // UMMAYA citation extension — augments context at runtime for permission UI.
@@ -52,15 +57,18 @@ type ContextWithCitation = ToolUseContext & {
52
57
  // ---------------------------------------------------------------------------
53
58
 
54
59
  const inputSchema = lazySchema(() =>
55
- z.strictObject({
56
- tool_id: z
57
- .string()
58
- .min(1)
59
- .describe('Registered adapter identifier (obtain via find mode=search)'),
60
- params: z
61
- .record(z.string(), z.unknown())
62
- .describe('Adapter-defined Pydantic-validated parameter body'),
63
- }),
60
+ z.preprocess(
61
+ value => normalizeRootPrimitiveAdapterEnvelope(SEND_TOOL_NAME, value),
62
+ z.strictObject({
63
+ tool_id: z
64
+ .string()
65
+ .min(1)
66
+ .describe('Registered send adapter identifier from <available_adapters>'),
67
+ params: z
68
+ .record(z.string(), z.unknown())
69
+ .describe('Adapter-defined Pydantic-validated parameter body'),
70
+ }),
71
+ ),
64
72
  )
65
73
  type InputSchema = ReturnType<typeof inputSchema>
66
74
 
@@ -285,6 +293,14 @@ export const SubmitPrimitive = buildTool({
285
293
  // 1002 contract (Spec 024 invariant: every adapter cites the agency
286
294
  // policy URL) is enforced at the primitive surface.
287
295
 
296
+ if (isRootPrimitiveToolId(input.tool_id)) {
297
+ return {
298
+ result: false as const,
299
+ message: rootPrimitiveSelfTargetMessage(input.tool_id, 'send'),
300
+ errorCode: PrimitiveErrorCode.AdapterNotFound,
301
+ }
302
+ }
303
+
288
304
  // Tier 1 — synced backend manifest (FR-017).
289
305
  if (isManifestSynced()) {
290
306
  const backendEntry = resolveAdapter(input.tool_id)
@@ -37,6 +37,11 @@ import {
37
37
  } from '../_shared/compactPrimitiveResult.js'
38
38
  import { getOrCreateUmmayaBridge } from '../../ipc/bridgeSingleton.js'
39
39
  import { getOrCreatePendingCallRegistry } from '../../ipc/pendingCallSingleton.js'
40
+ import {
41
+ isRootPrimitiveToolId,
42
+ normalizeRootPrimitiveAdapterEnvelope,
43
+ rootPrimitiveSelfTargetMessage,
44
+ } from '../_shared/rootPrimitiveInput.js'
40
45
 
41
46
  // ---------------------------------------------------------------------------
42
47
  // UMMAYA citation extension — augments context at runtime for permission UI.
@@ -52,15 +57,18 @@ type ContextWithCitation = ToolUseContext & {
52
57
  // ---------------------------------------------------------------------------
53
58
 
54
59
  const inputSchema = lazySchema(() =>
55
- z.strictObject({
56
- tool_id: z
57
- .string()
58
- .min(1)
59
- .describe('Auth adapter identifier, e.g. "gongdong_injeungseo", "mobile_id"'),
60
- params: z
61
- .record(z.string(), z.unknown())
62
- .describe('Adapter-defined credential parameter body'),
63
- }),
60
+ z.preprocess(
61
+ value => normalizeRootPrimitiveAdapterEnvelope(CHECK_TOOL_NAME, value),
62
+ z.strictObject({
63
+ tool_id: z
64
+ .string()
65
+ .min(1)
66
+ .describe('Auth adapter identifier, e.g. "mock_verify_mobile_id"'),
67
+ params: z
68
+ .record(z.string(), z.unknown())
69
+ .describe('Adapter-defined credential parameter body'),
70
+ }),
71
+ ),
64
72
  )
65
73
  type InputSchema = ReturnType<typeof inputSchema>
66
74
 
@@ -276,6 +284,14 @@ export const VerifyPrimitive = buildTool({
276
284
  // Epic ε #2296 T013 — two-tier resolution (FR-017 / FR-018 / FR-019 / FR-020).
277
285
  // Spec 2521 (2026-05-01) — citation-missing branch added (1002).
278
286
 
287
+ if (isRootPrimitiveToolId(input.tool_id)) {
288
+ return {
289
+ result: false as const,
290
+ message: rootPrimitiveSelfTargetMessage(input.tool_id, 'check'),
291
+ errorCode: PrimitiveErrorCode.AdapterNotFound,
292
+ }
293
+ }
294
+
279
295
  // Tier 1 — synced backend manifest (FR-017).
280
296
  if (isManifestSynced()) {
281
297
  const backendEntry = resolveAdapter(input.tool_id)
@@ -0,0 +1,49 @@
1
+ const SYNTHETIC_USER_CONTEXT_RE =
2
+ /<available_adapters\b|<\/available_adapters>|<available-deferred-tools\b|<\/available-deferred-tools>|##\s*Available tools|Current session context|Concrete UMMAYA|Pick a concrete adapter from <available_adapters>|Prefer concrete adapter function calls|<tool_use_error\b|<\/tool_use_error>|AdapterNotFound:|Permission delegation required:|tool-choice mismatch|Required follow-up for this tool chain|Emergency evidence chain complete|KMA analyzed weather-chart|Protected-domain/iu
3
+
4
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
5
+ return typeof value === 'object' && value !== null
6
+ ? (value as Record<string, unknown>)
7
+ : undefined
8
+ }
9
+
10
+ function messageContent(message: unknown): unknown {
11
+ const outer = asRecord(message)
12
+ const inner = asRecord(outer?.message)
13
+ return inner?.content ?? outer?.content
14
+ }
15
+
16
+ function contentHasToolResult(content: unknown): boolean {
17
+ if (!Array.isArray(content)) return false
18
+ return content.some(block => asRecord(block)?.type === 'tool_result')
19
+ }
20
+
21
+ export function isSyntheticUserMessage(message: unknown): boolean {
22
+ const outer = asRecord(message)
23
+ const inner = asRecord(outer?.message)
24
+ return (
25
+ outer?.isMeta === true ||
26
+ inner?.isMeta === true ||
27
+ outer?.isCompactSummary === true ||
28
+ outer?.isVisibleInTranscriptOnly === true ||
29
+ outer?.toolUseResult !== undefined ||
30
+ outer?.sourceToolAssistantUUID !== undefined ||
31
+ contentHasToolResult(messageContent(message))
32
+ )
33
+ }
34
+
35
+ export function isSyntheticUserText(text: string): boolean {
36
+ const trimmed = text.trim()
37
+ return trimmed.length > 0 && SYNTHETIC_USER_CONTEXT_RE.test(trimmed)
38
+ }
39
+
40
+ export function isNonSyntheticUserText(text: string): boolean {
41
+ return text.trim().length > 0 && !isSyntheticUserText(text)
42
+ }
43
+
44
+ export function isNonSyntheticUserMessageText(
45
+ message: unknown,
46
+ text: string,
47
+ ): boolean {
48
+ return !isSyntheticUserMessage(message) && isNonSyntheticUserText(text)
49
+ }