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
@@ -18,7 +18,7 @@ import { trace, SpanStatusCode } from '@opentelemetry/api'
18
18
  import type { Span } from '@opentelemetry/api'
19
19
  import type { IPCBridge } from './bridge.js'
20
20
  import { makeUUIDv7, makeBaseEnvelope } from './envelope.js'
21
- import type { AssistantChunkFrame, BackpressureSignalFrame, ChatMessage as IPCChatMessage, ChatRequestFrame, ErrorFrame, ToolCallFrame, ToolResultFrame, ToolDefinition as IPCToolDefinition } from './frames.generated.js'
21
+ import type { AssistantChunkFrame, BackpressureSignalFrame, ChatMessage as IPCChatMessage, ChatRequestFrame, ErrorFrame, ProgressEventFrame, ToolCallFrame, ToolResultFrame, ToolDefinition as IPCToolDefinition } from './frames.generated.js'
22
22
  import type { PendingCallRegistry } from '../tools/_shared/pendingCallRegistry.js'
23
23
  import { getOrCreatePendingCallRegistry } from './pendingCallSingleton.js'
24
24
  import type {
@@ -80,9 +80,8 @@ interface _TurnAccumulator {
80
80
  contentBlocks: UmmayaContentBlockParam[]
81
81
  usage: UmmayaUsage
82
82
  stopReason: UmmayaStopReason
83
- /** Index of the single text block. UMMAYA streams text into one block per
84
- * turn, so this stays at 0 and is the target of every `content_block_delta`
85
- * and the final `content_block_stop` for text. */
83
+ /** Index of the current text block. It is opened lazily on the first
84
+ * CC-compatible text delta, not at message_start. */
86
85
  blockIndex: number
87
86
  /** Monotonic counter for tool_use blocks within this turn. The nth
88
87
  * tool_use block lands at index `blockIndex + n` (so text is 0, tool
@@ -94,9 +93,11 @@ interface _TurnAccumulator {
94
93
  * channel is forwarded by the backend as `AssistantChunkFrame.thinking`;
95
94
  * llmClient mirrors CC's claude.ts:2148-2165 by routing those chunks to
96
95
  * a dedicated thinking content block (`type: 'thinking'`). The block
97
- * is opened lazily on the first thinking delta and closed at the same
98
- * time as the text block (done frame). Undefined until first delta. */
96
+ * is opened lazily on the first thinking delta and closed before text/tool
97
+ * blocks, matching CC's one-open-content-block stream shape. */
99
98
  thinkingBlockIndex: number | undefined
99
+ /** Currently open non-tool block. CC closes text/thinking before tool_use. */
100
+ openBlockIndex: number | undefined
100
101
  seenFirstChunk: boolean
101
102
  }
102
103
 
@@ -106,9 +107,10 @@ function _defaultAccumulator(): _TurnAccumulator {
106
107
  contentBlocks: [],
107
108
  usage: { input_tokens: 0, output_tokens: 0 },
108
109
  stopReason: 'end_turn',
109
- blockIndex: 0,
110
+ blockIndex: -1,
110
111
  toolBlockCounter: 0,
111
112
  thinkingBlockIndex: undefined,
113
+ openBlockIndex: undefined,
112
114
  seenFirstChunk: false,
113
115
  }
114
116
  }
@@ -299,6 +301,9 @@ export class LLMClient {
299
301
  ...(params.system !== undefined ? { system: params.system } : {}),
300
302
  max_tokens: params.max_tokens,
301
303
  ...(params.temperature !== undefined ? { temperature: params.temperature } : {}),
304
+ ...(params.reasoning_mode !== undefined
305
+ ? { reasoning_mode: params.reasoning_mode }
306
+ : {}),
302
307
  }
303
308
 
304
309
  // ----------------------------------------------------------------
@@ -339,11 +344,109 @@ export class LLMClient {
339
344
  // primitive bridge for all tool dispatch; no server-side tools
340
345
  // ----------------------------------------------------------------
341
346
  let streamDone = false
347
+ const model = this.model
348
+ const ensureMessageStart = function* (
349
+ messageId: string,
350
+ ): Generator<UmmayaRawMessageStreamEvent, void, unknown> {
351
+ if (acc.seenFirstChunk) return
352
+ acc.seenFirstChunk = true
353
+ acc.messageId = messageId
354
+ yield {
355
+ type: 'message_start',
356
+ message: {
357
+ id: messageId,
358
+ role: 'assistant',
359
+ model,
360
+ },
361
+ } satisfies UmmayaRawMessageStreamEvent
362
+ }
363
+
364
+ const closeOpenBlock = function* (): Generator<
365
+ UmmayaRawMessageStreamEvent,
366
+ void,
367
+ unknown
368
+ > {
369
+ if (acc.openBlockIndex === undefined) return
370
+ yield {
371
+ type: 'content_block_stop',
372
+ index: acc.openBlockIndex,
373
+ } satisfies UmmayaRawMessageStreamEvent
374
+ acc.openBlockIndex = undefined
375
+ }
376
+
377
+ const ensureTextBlock = function* (): Generator<
378
+ UmmayaRawMessageStreamEvent,
379
+ void,
380
+ unknown
381
+ > {
382
+ if (acc.openBlockIndex === acc.blockIndex && acc.blockIndex >= 0) {
383
+ return
384
+ }
385
+ yield* closeOpenBlock()
386
+ const textIndex = acc.contentBlocks.length
387
+ acc.blockIndex = textIndex
388
+ acc.contentBlocks[textIndex] = { type: 'text', text: '' }
389
+ acc.openBlockIndex = textIndex
390
+ yield {
391
+ type: 'content_block_start',
392
+ index: textIndex,
393
+ content_block: { type: 'text', text: '' },
394
+ } satisfies UmmayaRawMessageStreamEvent
395
+ }
396
+
397
+ const ensureThinkingBlock = function* (): Generator<
398
+ UmmayaRawMessageStreamEvent,
399
+ void,
400
+ unknown
401
+ > {
402
+ if (
403
+ acc.thinkingBlockIndex !== undefined &&
404
+ acc.openBlockIndex === acc.thinkingBlockIndex
405
+ ) {
406
+ return
407
+ }
408
+ yield* closeOpenBlock()
409
+ const thinkingIdx = acc.contentBlocks.length
410
+ acc.thinkingBlockIndex = thinkingIdx
411
+ acc.contentBlocks[thinkingIdx] = { type: 'thinking', thinking: '' }
412
+ acc.openBlockIndex = thinkingIdx
413
+ yield {
414
+ type: 'content_block_start',
415
+ index: thinkingIdx,
416
+ content_block: { type: 'thinking', thinking: '' },
417
+ } satisfies UmmayaRawMessageStreamEvent
418
+ }
342
419
 
343
420
  for await (const frame of this.bridge.frames()) {
344
421
  // Filter: only process frames for this turn's correlation_id.
345
422
  if (frame.correlation_id !== correlationId) continue
346
423
 
424
+ // ---- ProgressEventFrame ----------------------------------------
425
+ // Deterministic harness progress. This is safe UI state, not provider
426
+ // reasoning. It paints even when reasoning mode suppresses thinking.
427
+ if (frame.kind === 'progress_event') {
428
+ const progress = frame as ProgressEventFrame
429
+ yield* ensureMessageStart(`progress-${correlationId}`)
430
+ yield* ensureTextBlock()
431
+
432
+ const progressText = `${
433
+ progress.message_ko ?? progress.message_en ?? progress.phase
434
+ }\n`
435
+ yield {
436
+ type: 'content_block_delta',
437
+ index: acc.blockIndex,
438
+ delta: {
439
+ type: 'text_delta',
440
+ text: progressText,
441
+ },
442
+ } satisfies UmmayaRawMessageStreamEvent
443
+ const existing = acc.contentBlocks[acc.blockIndex]
444
+ if (existing && existing.type === 'text') {
445
+ existing.text += progressText
446
+ }
447
+ continue
448
+ }
449
+
347
450
  // ---- AssistantChunkFrame ----------------------------------------
348
451
  // CC reference: services/api/claude.ts:1980-2169 (the message_start +
349
452
  // content_block_start + content_block_delta + content_block_stop
@@ -356,26 +459,7 @@ export class LLMClient {
356
459
  // CC reference: services/api/claude.ts:1980 (message_start),
357
460
  // services/api/claude.ts:2019 (text content_block_start).
358
461
  if (!acc.seenFirstChunk) {
359
- acc.seenFirstChunk = true
360
- acc.messageId = chunk.message_id
361
-
362
- yield {
363
- type: 'message_start',
364
- message: {
365
- id: chunk.message_id,
366
- role: 'assistant',
367
- model: this.model,
368
- },
369
- } satisfies UmmayaRawMessageStreamEvent
370
-
371
- yield {
372
- type: 'content_block_start',
373
- index: 0,
374
- content_block: { type: 'text', text: '' },
375
- } satisfies UmmayaRawMessageStreamEvent
376
-
377
- acc.blockIndex = 0
378
- acc.contentBlocks[0] = { type: 'text', text: '' }
462
+ yield* ensureMessageStart(chunk.message_id)
379
463
  }
380
464
 
381
465
  // Thinking delta — K-EXAONE's reasoning_content channel arrives
@@ -384,17 +468,8 @@ export class LLMClient {
384
468
  // `type: 'thinking'` blocks and route them to the
385
469
  // AssistantThinkingMessage component (∴ Thinking dim italic).
386
470
  if (chunk.thinking && chunk.thinking.length > 0) {
387
- if (acc.thinkingBlockIndex === undefined) {
388
- const thinkingIdx = acc.contentBlocks.length
389
- acc.thinkingBlockIndex = thinkingIdx
390
- yield {
391
- type: 'content_block_start',
392
- index: thinkingIdx,
393
- content_block: { type: 'thinking', thinking: '' },
394
- } satisfies UmmayaRawMessageStreamEvent
395
- acc.contentBlocks[thinkingIdx] = { type: 'thinking', thinking: '' }
396
- }
397
- const thinkingIdx = acc.thinkingBlockIndex
471
+ yield* ensureThinkingBlock()
472
+ const thinkingIdx = acc.thinkingBlockIndex!
398
473
  yield {
399
474
  type: 'content_block_delta',
400
475
  index: thinkingIdx,
@@ -408,6 +483,7 @@ export class LLMClient {
408
483
 
409
484
  // Emit text delta (even if delta is empty — forward compat).
410
485
  if (chunk.delta.length > 0) {
486
+ yield* ensureTextBlock()
411
487
  yield {
412
488
  type: 'content_block_delta',
413
489
  index: acc.blockIndex,
@@ -429,22 +505,12 @@ export class LLMClient {
429
505
  // If we never saw a first chunk (edge: backend sends done=true
430
506
  // immediately on an empty response), bootstrap the message events.
431
507
  if (!acc.seenFirstChunk) {
432
- acc.seenFirstChunk = true
433
- acc.messageId = chunk.message_id
434
- yield {
435
- type: 'message_start',
436
- message: { id: chunk.message_id, role: 'assistant', model: this.model },
437
- } satisfies UmmayaRawMessageStreamEvent
438
- yield {
439
- type: 'content_block_start',
440
- index: 0,
441
- content_block: { type: 'text', text: '' },
442
- } satisfies UmmayaRawMessageStreamEvent
443
- acc.blockIndex = 0
508
+ yield* ensureMessageStart(chunk.message_id)
444
509
  }
445
510
 
446
511
  // Emit any final delta text if present.
447
512
  if (chunk.delta.length > 0) {
513
+ yield* ensureTextBlock()
448
514
  yield {
449
515
  type: 'content_block_delta',
450
516
  index: acc.blockIndex,
@@ -463,7 +529,7 @@ export class LLMClient {
463
529
  acc.usage = usage
464
530
 
465
531
  // CC reference: services/api/claude.ts:2171 (content_block_stop).
466
- yield { type: 'content_block_stop', index: acc.blockIndex } satisfies UmmayaRawMessageStreamEvent
532
+ yield* closeOpenBlock()
467
533
 
468
534
  // CC reference: services/api/claude.ts:2213 (message_delta with
469
535
  // stop_reason + usage).
@@ -490,6 +556,8 @@ export class LLMClient {
490
556
  // initial content_block_start payload.
491
557
  else if (frame.kind === 'tool_call') {
492
558
  const toolFrame = frame as ToolCallFrame
559
+ yield* ensureMessageStart(`tool-${correlationId}`)
560
+ yield* closeOpenBlock()
493
561
  // tool_call frames may arrive interleaved with text streaming AND
494
562
  // thinking streaming in a multi-turn / parallel-tool / K-EXAONE
495
563
  // reasoning scenario. Allocate the tool block at the next free
@@ -526,6 +594,17 @@ export class LLMClient {
526
594
  name: toolFrame.name,
527
595
  input: toolFrame.arguments as Record<string, unknown>,
528
596
  }
597
+
598
+ acc.stopReason = 'tool_use'
599
+ yield {
600
+ type: 'message_delta',
601
+ delta: { stop_reason: 'tool_use' },
602
+ usage: acc.usage,
603
+ } satisfies UmmayaRawMessageStreamEvent
604
+ yield { type: 'message_stop' } satisfies UmmayaRawMessageStreamEvent
605
+
606
+ streamDone = true
607
+ break
529
608
  }
530
609
 
531
610
  // ---- ToolResultFrame (I-D5 + Epic ζ smoke checkpoint, FR-013) ----
@@ -66,6 +66,7 @@ export type UmmayaMessageStreamParams = {
66
66
  tools?: UmmayaToolDefinition[]
67
67
  max_tokens: number
68
68
  temperature?: number
69
+ reasoning_mode?: import('../utils/kExaoneReasoning.js').ReasoningMode
69
70
  metadata?: Record<string, string>
70
71
  }
71
72
 
@@ -121,10 +122,24 @@ export type UmmayaThinkingDelta = {
121
122
  thinking: string
122
123
  }
123
124
 
125
+ export type UmmayaProgressDelta = {
126
+ type: 'progress_event'
127
+ phase: 'analysis' | 'tool_selection' | 'tool_call' | 'tool_result' | 'answer_synthesis'
128
+ message_ko: string
129
+ message_en: string
130
+ safe_to_persist: boolean
131
+ tool_id?: string | null
132
+ call_id?: string | null
133
+ }
134
+
124
135
  export type UmmayaContentBlockDelta = {
125
136
  type: 'content_block_delta'
126
137
  index: number
127
- delta: UmmayaTextDelta | UmmayaInputJsonDelta | UmmayaThinkingDelta
138
+ delta:
139
+ | UmmayaTextDelta
140
+ | UmmayaInputJsonDelta
141
+ | UmmayaThinkingDelta
142
+ | UmmayaProgressDelta
128
143
  }
129
144
 
130
145
  export type UmmayaContentBlockStop = {