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
@@ -128,6 +128,7 @@ import { feature } from 'bun:bundle'
128
128
  // aliases (ClientOptions, APIError, APIConnectionTimeoutError, APIUserAbortError
129
129
  // all re-exported by sdk-compat.ts as structural stubs).
130
130
  import type { ClientOptions } from '../../sdk-compat.js'
131
+ import type { ReasoningMode } from '../../utils/kExaoneReasoning.js'
131
132
  import {
132
133
  APIConnectionTimeoutError,
133
134
  APIError,
@@ -216,9 +217,11 @@ import {
216
217
  TOOL_SEARCH_TOOL_NAME,
217
218
  } from '../../tools/ToolSearchTool/prompt.js'
218
219
  import {
219
- isRootPrimitiveToolName,
220
+ getAdapterToolByName,
220
221
  selectTopKAdapterToolNamesForQuery,
221
222
  } from '../../tools/AdapterTool/AdapterTool.js'
223
+ import { isNonSyntheticUserText } from '../../tools/_shared/citizenUserText.js'
224
+ import { shouldSuppressUmmayaToolCallsForAnswerSynthesis } from '../../tools/_shared/toolChoiceRepair.js'
222
225
  import { count } from '../../utils/array.js'
223
226
  import { insertBlockAfterToolResults } from '../../utils/contentArray.js'
224
227
  import { validateBoundedIntEnvVar } from '../../utils/envValidation.js'
@@ -723,6 +726,7 @@ export type Options = {
723
726
  skipCacheWrite?: boolean
724
727
  temperatureOverride?: number
725
728
  effortValue?: EffortValue
729
+ reasoningMode?: ReasoningMode
726
730
  mcpTools: Tools
727
731
  hasPendingMcpServers?: boolean
728
732
  queryTracking?: QueryChainTracking
@@ -833,7 +837,7 @@ function latestUserTextForToolRetrieval(messages: Message[]): string {
833
837
  if (message?.type !== 'user') continue
834
838
  const content = message.message?.content
835
839
  if (typeof content === 'string') {
836
- if (content.trim().length > 0) return content
840
+ if (isNonSyntheticUserText(content)) return content
837
841
  continue
838
842
  }
839
843
  if (Array.isArray(content)) {
@@ -844,7 +848,7 @@ function latestUserTextForToolRetrieval(messages: Message[]): string {
844
848
  )
845
849
  .map(block => block.text)
846
850
  .join('')
847
- if (text.trim().length > 0) return text
851
+ if (isNonSyntheticUserText(text)) return text
848
852
  }
849
853
  }
850
854
  return ''
@@ -1183,10 +1187,36 @@ async function* queryModel(
1183
1187
  'query',
1184
1188
  )
1185
1189
 
1186
- // Precompute once isDeferredTool does 2 GrowthBook lookups per call
1190
+ const turnLocalAdapterToolNames = new Set(
1191
+ selectTopKAdapterToolNamesForQuery(
1192
+ latestUserTextForToolRetrieval(messages),
1193
+ ),
1194
+ )
1195
+ if (options.toolChoice?.type === 'tool') {
1196
+ turnLocalAdapterToolNames.add(options.toolChoice.name)
1197
+ }
1198
+ if (turnLocalAdapterToolNames.size > 0) {
1199
+ logForDebugging(
1200
+ `UMMAYA turn-local adapter schemas: ${[...turnLocalAdapterToolNames].join(', ')}`,
1201
+ )
1202
+ }
1203
+ const requestTools =
1204
+ turnLocalAdapterToolNames.size === 0
1205
+ ? tools
1206
+ : [
1207
+ ...tools,
1208
+ ...[...turnLocalAdapterToolNames]
1209
+ .filter(toolName => !tools.some(tool => tool.name === toolName))
1210
+ .map(toolName => getAdapterToolByName(toolName))
1211
+ .filter((tool): tool is NonNullable<typeof tool> => Boolean(tool)),
1212
+ ]
1213
+
1214
+ // Precompute once — isDeferredTool does 2 GrowthBook lookups per call.
1215
+ // Include turn-local synced adapters even if the long-lived TUI tool pool
1216
+ // was assembled before the latest backend manifest frame arrived.
1187
1217
  const deferredToolNames = new Set<string>()
1188
1218
  if (useToolSearch) {
1189
- for (const t of tools) {
1219
+ for (const t of requestTools) {
1190
1220
  if (isDeferredTool(t)) deferredToolNames.add(t.name)
1191
1221
  }
1192
1222
  }
@@ -1204,32 +1234,29 @@ async function* queryModel(
1204
1234
  )
1205
1235
  useToolSearch = false
1206
1236
  }
1207
-
1208
- const turnLocalAdapterToolNames = new Set(
1209
- selectTopKAdapterToolNamesForQuery(
1210
- latestUserTextForToolRetrieval(messages),
1211
- ),
1212
- )
1213
- if (turnLocalAdapterToolNames.size > 0) {
1214
- logForDebugging(
1215
- `UMMAYA turn-local adapter schemas: ${[...turnLocalAdapterToolNames].join(', ')}`,
1216
- )
1237
+ const suppressUmmayaToolCalls =
1238
+ shouldSuppressUmmayaToolCallsForAnswerSynthesis({ messages, tools: requestTools })
1239
+ if (suppressUmmayaToolCalls) {
1240
+ logForDebugging('UMMAYA suppressing tool schemas for answer synthesis')
1217
1241
  }
1218
1242
 
1219
1243
  // Filter out ToolSearchTool if tool search is not enabled for this model
1220
1244
  // ToolSearchTool returns tool_reference blocks which unsupported models can't handle
1221
1245
  let filteredTools: Tools
1222
1246
 
1223
- if (useToolSearch) {
1247
+ if (suppressUmmayaToolCalls) {
1248
+ filteredTools = []
1249
+ } else if (useToolSearch) {
1224
1250
  // Dynamic tool loading: Only include deferred tools that have been discovered
1225
1251
  // via tool_reference blocks in the message history. This eliminates the need
1226
1252
  // to predeclare all deferred tools upfront and removes limits on tool quantity.
1227
1253
  const discoveredToolNames = extractDiscoveredToolNames(messages)
1228
1254
 
1229
- filteredTools = tools.filter(tool => {
1230
- if (turnLocalAdapterToolNames.size > 0 && isRootPrimitiveToolName(tool.name)) {
1231
- return false
1232
- }
1255
+ filteredTools = requestTools.filter(tool => {
1256
+ // 0.2.1 exposed the lightweight root primitives together with concrete
1257
+ // adapter schemas. Keep that surface so K-EXAONE preserves CC-style
1258
+ // prose→tool→prose loop painting, while still limiting concrete adapter
1259
+ // schemas to the turn-local top-k set.
1233
1260
  if (turnLocalAdapterToolNames.has(tool.name)) return true
1234
1261
  // Always include non-deferred tools
1235
1262
  if (!deferredToolNames.has(tool.name)) return true
@@ -1239,11 +1266,10 @@ async function* queryModel(
1239
1266
  return discoveredToolNames.has(tool.name)
1240
1267
  })
1241
1268
  } else {
1242
- filteredTools = tools.filter(t => {
1269
+ filteredTools = requestTools.filter(t => {
1243
1270
  if (toolMatchesName(t, TOOL_SEARCH_TOOL_NAME)) return false
1244
- if (turnLocalAdapterToolNames.size > 0 && isRootPrimitiveToolName(t.name)) {
1245
- return false
1246
- }
1271
+ // Keep non-deferred root primitives even when concrete top-k adapter
1272
+ // schemas are available; this matches the released 0.2.1 loop surface.
1247
1273
  if (isDeferredTool(t)) return turnLocalAdapterToolNames.has(t.name)
1248
1274
  return true
1249
1275
  })
@@ -1803,6 +1829,9 @@ async function* queryModel(
1803
1829
  output_config: outputConfig,
1804
1830
  }),
1805
1831
  ...(speed !== undefined && { speed }),
1832
+ ...(options.reasoningMode !== undefined && {
1833
+ reasoning_mode: options.reasoningMode,
1834
+ }),
1806
1835
  }
1807
1836
  }
1808
1837
 
@@ -2346,7 +2375,7 @@ async function* queryModel(
2346
2375
  max_tokens: maxOutputTokens,
2347
2376
  })
2348
2377
  yield createAssistantAPIErrorMessage({
2349
- content: `${API_ERROR_MESSAGE_PREFIX}: Claude's response exceeded the ${
2378
+ content: `${API_ERROR_MESSAGE_PREFIX}: Ummaya's response exceeded the ${
2350
2379
  maxOutputTokens
2351
2380
  } output token maximum. To configure this behavior, set the CLAUDE_CODE_MAX_OUTPUT_TOKENS environment variable.`,
2352
2381
  apiError: 'max_output_tokens',
@@ -26,6 +26,11 @@ import {
26
26
  } from '../../sdk-compat.js'
27
27
  import { assertFriendliApiKeyForUse } from '../../utils/auth.js'
28
28
  import { getUserAgent } from '../../utils/http.js'
29
+ import {
30
+ providerReasoningPayload,
31
+ resolveKExaoneReasoningPolicy,
32
+ type ReasoningMode,
33
+ } from '../../utils/kExaoneReasoning.js'
29
34
  import { UMMAYA_K_EXAONE_MODEL } from '../../utils/model/constants.js'
30
35
 
31
36
  export const CLIENT_REQUEST_ID_HEADER = 'x-client-request-id'
@@ -41,6 +46,11 @@ type RequestOptions = {
41
46
  headers?: Record<string, string>
42
47
  }
43
48
 
49
+ type FriendliMessageStreamParams = BetaMessageStreamParams & {
50
+ stream?: boolean
51
+ reasoning_mode?: ReasoningMode
52
+ }
53
+
44
54
  type FriendliClientArgs = {
45
55
  apiKey?: string
46
56
  maxRetries: number
@@ -130,7 +140,7 @@ class FriendliMessagesCompatClient {
130
140
  readonly beta: {
131
141
  messages: {
132
142
  create: (
133
- params: BetaMessageStreamParams & { stream?: boolean },
143
+ params: FriendliMessageStreamParams,
134
144
  options?: RequestOptions,
135
145
  ) => unknown
136
146
  }
@@ -159,7 +169,7 @@ class FriendliMessagesCompatClient {
159
169
  }
160
170
 
161
171
  private async streamWithResponse(
162
- params: BetaMessageStreamParams & { stream?: boolean },
172
+ params: FriendliMessageStreamParams,
163
173
  options?: RequestOptions,
164
174
  ): Promise<{
165
175
  data: Stream<BetaRawMessageStreamEvent>
@@ -188,7 +198,7 @@ class FriendliMessagesCompatClient {
188
198
  }
189
199
 
190
200
  private async complete(
191
- params: BetaMessageStreamParams,
201
+ params: FriendliMessageStreamParams,
192
202
  options?: RequestOptions,
193
203
  ): Promise<BetaMessage> {
194
204
  const response = await this.fetchChatCompletion(
@@ -279,6 +289,9 @@ class FriendliMessagesCompatClient {
279
289
  const toolStates = new Map<number, ToolStreamState>()
280
290
  let finalUsage: BetaUsage | undefined
281
291
  let stopReason: BetaMessage['stop_reason'] = 'end_turn'
292
+ const allowReasoning = resolveKExaoneReasoningPolicy({
293
+ explicitSessionMode: params.reasoning_mode,
294
+ }).includeReasoning
282
295
 
283
296
  const ensureMessageStart = function* () {
284
297
  if (messageStarted) return
@@ -301,6 +314,12 @@ class FriendliMessagesCompatClient {
301
314
  const ensureTextBlock = function* () {
302
315
  yield* ensureMessageStart()
303
316
  if (textBlockIndex === undefined) {
317
+ if (
318
+ thinkingBlockIndex !== undefined &&
319
+ openedBlocks.has(thinkingBlockIndex)
320
+ ) {
321
+ yield* closeNonToolBlocks()
322
+ }
304
323
  textBlockIndex = nextBlockIndex++
305
324
  openedBlocks.add(textBlockIndex)
306
325
  yield {
@@ -314,6 +333,9 @@ class FriendliMessagesCompatClient {
314
333
  const ensureThinkingBlock = function* () {
315
334
  yield* ensureMessageStart()
316
335
  if (thinkingBlockIndex === undefined) {
336
+ if (textBlockIndex !== undefined && openedBlocks.has(textBlockIndex)) {
337
+ yield* closeNonToolBlocks()
338
+ }
317
339
  thinkingBlockIndex = nextBlockIndex++
318
340
  openedBlocks.add(thinkingBlockIndex)
319
341
  yield {
@@ -385,7 +407,7 @@ class FriendliMessagesCompatClient {
385
407
 
386
408
  for (const choice of chunk.choices ?? []) {
387
409
  const delta = choice.delta ?? {}
388
- if (delta.reasoning_content) {
410
+ if (delta.reasoning_content && allowReasoning) {
389
411
  yield* ensureThinkingBlock()
390
412
  yield {
391
413
  type: 'content_block_delta' as const,
@@ -454,17 +476,20 @@ class FriendliMessagesCompatClient {
454
476
  }
455
477
 
456
478
  function buildOpenAIPayload(
457
- params: BetaMessageStreamParams & { stream?: boolean },
479
+ params: FriendliMessageStreamParams,
458
480
  ): Record<string, unknown> {
459
481
  const messages = convertMessages(params.messages, params.system)
460
482
  const tools = convertTools(params.tools)
483
+ const reasoning = providerReasoningPayload(
484
+ resolveKExaoneReasoningPolicy({
485
+ explicitSessionMode: params.reasoning_mode,
486
+ }),
487
+ )
461
488
  const payload: Record<string, unknown> = {
462
489
  model: params.model || UMMAYA_K_EXAONE_MODEL,
463
490
  messages,
464
491
  max_tokens: params.max_tokens,
465
- chat_template_kwargs: {
466
- enable_thinking: isTruthy(process.env.UMMAYA_K_EXAONE_THINKING),
467
- },
492
+ ...reasoning,
468
493
  ...(params.temperature !== undefined ? { temperature: params.temperature } : {}),
469
494
  ...(tools.length > 0
470
495
  ? {
@@ -740,7 +765,3 @@ function resolveFetch(fetchOverride: unknown): FetchLike {
740
765
  function trimSlash(value: string): string {
741
766
  return value.replace(/\/+$/, '')
742
767
  }
743
-
744
- function isTruthy(value: string | undefined): boolean {
745
- return value === '1' || value?.toLowerCase() === 'true' || value?.toLowerCase() === 'yes'
746
- }
@@ -55,8 +55,11 @@ export async function* queryModelWithStreaming(params: {
55
55
  const persistThinking = process.env.UMMAYA_PERSIST_THINKING === '1'
56
56
  let accumulatedThinking = ''
57
57
  let messageStartEmitted = false
58
- let textBlockStarted = false
58
+ let nextContentBlockIndex = 0
59
+ let textBlockIndex: number | null = null
59
60
  let textBlockStopped = false
61
+ let thinkingBlockIndex: number | null = null
62
+ let thinkingBlockStopped = false
60
63
  const pendingContentBlocks: Array<{
61
64
  type: 'tool_use'
62
65
  id: string
@@ -119,16 +122,17 @@ export async function* queryModelWithStreaming(params: {
119
122
  }
120
123
  messageStartEmitted = true
121
124
  }
122
- if (!textBlockStarted) {
125
+ if (thinkingText.length > 0 && thinkingBlockIndex === null) {
123
126
  yield {
124
127
  type: 'stream_event' as const,
125
128
  event: {
126
129
  type: 'content_block_start' as const,
127
- index: 0,
128
- content_block: { type: 'text' as const, text: '' },
130
+ index: nextContentBlockIndex,
131
+ content_block: { type: 'thinking' as const, thinking: '' },
129
132
  },
130
133
  }
131
- textBlockStarted = true
134
+ thinkingBlockIndex = nextContentBlockIndex
135
+ nextContentBlockIndex += 1
132
136
  }
133
137
 
134
138
  if (thinkingText.length > 0) {
@@ -139,7 +143,7 @@ export async function* queryModelWithStreaming(params: {
139
143
  type: 'stream_event' as const,
140
144
  event: {
141
145
  type: 'content_block_delta' as const,
142
- index: 0,
146
+ index: thinkingBlockIndex ?? 0,
143
147
  delta: { type: 'thinking_delta' as const, thinking: thinkingText },
144
148
  },
145
149
  }
@@ -147,21 +151,50 @@ export async function* queryModelWithStreaming(params: {
147
151
 
148
152
  accumulated += deltaText
149
153
  if (deltaText.length > 0) {
154
+ if (thinkingBlockIndex !== null && !thinkingBlockStopped) {
155
+ yield {
156
+ type: 'stream_event' as const,
157
+ event: {
158
+ type: 'content_block_stop' as const,
159
+ index: thinkingBlockIndex,
160
+ },
161
+ }
162
+ thinkingBlockStopped = true
163
+ }
164
+ if (textBlockIndex === null) {
165
+ yield {
166
+ type: 'stream_event' as const,
167
+ event: {
168
+ type: 'content_block_start' as const,
169
+ index: nextContentBlockIndex,
170
+ content_block: { type: 'text' as const, text: '' },
171
+ },
172
+ }
173
+ textBlockIndex = nextContentBlockIndex
174
+ nextContentBlockIndex += 1
175
+ }
150
176
  yield {
151
177
  type: 'stream_event' as const,
152
178
  event: {
153
179
  type: 'content_block_delta' as const,
154
- index: 0,
180
+ index: textBlockIndex,
155
181
  delta: { type: 'text_delta' as const, text: deltaText },
156
182
  },
157
183
  }
158
184
  }
159
185
 
160
186
  if (frameAny.done) {
161
- if (textBlockStarted && !textBlockStopped) {
187
+ if (thinkingBlockIndex !== null && !thinkingBlockStopped) {
188
+ yield {
189
+ type: 'stream_event' as const,
190
+ event: { type: 'content_block_stop' as const, index: thinkingBlockIndex },
191
+ }
192
+ thinkingBlockStopped = true
193
+ }
194
+ if (textBlockIndex !== null && !textBlockStopped) {
162
195
  yield {
163
196
  type: 'stream_event' as const,
164
- event: { type: 'content_block_stop' as const, index: 0 },
197
+ event: { type: 'content_block_stop' as const, index: textBlockIndex },
165
198
  }
166
199
  textBlockStopped = true
167
200
  }
@@ -217,10 +250,17 @@ export async function* queryModelWithStreaming(params: {
217
250
  }
218
251
  messageStartEmitted = true
219
252
  }
220
- if (textBlockStarted && !textBlockStopped) {
253
+ if (thinkingBlockIndex !== null && !thinkingBlockStopped) {
221
254
  yield {
222
255
  type: 'stream_event' as const,
223
- event: { type: 'content_block_stop' as const, index: 0 },
256
+ event: { type: 'content_block_stop' as const, index: thinkingBlockIndex },
257
+ }
258
+ thinkingBlockStopped = true
259
+ }
260
+ if (textBlockIndex !== null && !textBlockStopped) {
261
+ yield {
262
+ type: 'stream_event' as const,
263
+ event: { type: 'content_block_stop' as const, index: textBlockIndex },
224
264
  }
225
265
  textBlockStopped = true
226
266
  }
@@ -232,7 +272,8 @@ export async function* queryModelWithStreaming(params: {
232
272
  }
233
273
 
234
274
  pendingContentBlocks.push(toolUseBlock)
235
- const toolBlockIndex = textBlockStarted ? 1 : 0
275
+ const toolBlockIndex = nextContentBlockIndex
276
+ nextContentBlockIndex += 1
236
277
 
237
278
  yield {
238
279
  type: 'stream_event' as const,
@@ -271,10 +312,17 @@ export async function* queryModelWithStreaming(params: {
271
312
  } else if (frameAny.kind === 'error') {
272
313
  const reason = frameAny.message ?? 'UMMAYA backend error'
273
314
  yield createAssistantMessage({ content: `[UMMAYA backend error] ${reason}` })
274
- if (textBlockStarted && !textBlockStopped) {
315
+ if (thinkingBlockIndex !== null && !thinkingBlockStopped) {
275
316
  yield {
276
317
  type: 'stream_event' as const,
277
- event: { type: 'content_block_stop' as const, index: 0 },
318
+ event: { type: 'content_block_stop' as const, index: thinkingBlockIndex },
319
+ }
320
+ thinkingBlockStopped = true
321
+ }
322
+ if (textBlockIndex !== null && !textBlockStopped) {
323
+ yield {
324
+ type: 'stream_event' as const,
325
+ event: { type: 'content_block_stop' as const, index: textBlockIndex },
278
326
  }
279
327
  textBlockStopped = true
280
328
  }
@@ -299,10 +347,16 @@ export async function* queryModelWithStreaming(params: {
299
347
  })
300
348
  }
301
349
  if (messageStartEmitted) {
302
- if (textBlockStarted && !textBlockStopped) {
350
+ if (thinkingBlockIndex !== null && !thinkingBlockStopped) {
351
+ yield {
352
+ type: 'stream_event' as const,
353
+ event: { type: 'content_block_stop' as const, index: thinkingBlockIndex },
354
+ }
355
+ }
356
+ if (textBlockIndex !== null && !textBlockStopped) {
303
357
  yield {
304
358
  type: 'stream_event' as const,
305
- event: { type: 'content_block_stop' as const, index: 0 },
359
+ event: { type: 'content_block_stop' as const, index: textBlockIndex },
306
360
  }
307
361
  }
308
362
  yield {
@@ -1,15 +1,15 @@
1
1
  import { registerBundledSkill } from '../bundledSkills.js'
2
2
 
3
- // Prompt text contains `ps` commands as instructions for Claude to run,
3
+ // Prompt text contains `ps` commands as instructions for UMMAYA to run,
4
4
  // not commands this file executes.
5
5
  // eslint-disable-next-line custom-rules/no-direct-ps-commands
6
- const STUCK_PROMPT = `# /stuck — diagnose frozen/slow Claude Code sessions
6
+ const STUCK_PROMPT = `# /stuck — diagnose frozen/slow UMMAYA sessions
7
7
 
8
- The user thinks another Claude Code session on this machine is frozen, stuck, or very slow. Investigate and post a report to #claude-code-feedback.
8
+ The user thinks another UMMAYA session on this machine is frozen, stuck, or very slow. Investigate and post a report to the configured UMMAYA feedback channel.
9
9
 
10
10
  ## What to look for
11
11
 
12
- Scan for other Claude Code processes (excluding the current one — PID is in \`process.pid\` but for shell commands just exclude the PID you see running this prompt). Process names are typically \`claude\` (installed) or \`cli\` (native dev build).
12
+ Scan for other UMMAYA processes (excluding the current one — PID is in \`process.pid\` but for shell commands just exclude the PID you see running this prompt). Process names are typically \`ummaya\` (installed) or \`cli\` (native dev build).
13
13
 
14
14
  Signs of a stuck session:
15
15
  - **High CPU (≥90%) sustained** — likely an infinite loop. Sample twice, 1-2s apart, to confirm it's not a transient spike.
@@ -21,17 +21,17 @@ Signs of a stuck session:
21
21
 
22
22
  ## Investigation steps
23
23
 
24
- 1. **List all Claude Code processes** (macOS/Linux):
24
+ 1. **List all UMMAYA processes** (macOS/Linux):
25
25
  \`\`\`
26
- ps -axo pid=,pcpu=,rss=,etime=,state=,comm=,command= | grep -E '(claude|cli)' | grep -v grep
26
+ ps -axo pid=,pcpu=,rss=,etime=,state=,comm=,command= | grep -E '(ummaya|cli)' | grep -v grep
27
27
  \`\`\`
28
- Filter to rows where \`comm\` is \`claude\` or (\`cli\` AND the command path contains "claude").
28
+ Filter to rows where \`comm\` is \`ummaya\` or (\`cli\` AND the command path contains "ummaya").
29
29
 
30
30
  2. **For anything suspicious**, gather more context:
31
31
  - Child processes: \`pgrep -lP <pid>\`
32
32
  - If high CPU: sample again after 1-2s to confirm it's sustained
33
33
  - If a child looks hung (e.g., a git command), note its full command line with \`ps -p <child_pid> -o command=\`
34
- - Check the session's debug log if you can infer the session ID: \`~/.claude/debug/<session-id>.txt\` (the last few hundred lines often show what it was doing before hanging)
34
+ - Check the session's debug log if you can infer the session ID: \`~/.ummaya/debug/<session-id>.txt\` (the last few hundred lines often show what it was doing before hanging)
35
35
 
36
36
  3. **Consider a stack dump** for a truly frozen process (advanced, optional):
37
37
  - macOS: \`sample <pid> 3\` gives a 3-second native stack sample
@@ -41,17 +41,17 @@ Signs of a stuck session:
41
41
 
42
42
  **Only post to Slack if you actually found something stuck.** If every session looks healthy, tell the user that directly — do not post an all-clear to the channel.
43
43
 
44
- If you did find a stuck/slow session, post to **#claude-code-feedback** (channel ID: \`C07VBSHV7EV\`) using the Slack MCP tool. Use ToolSearch to find \`slack_send_message\` if it's not already loaded.
44
+ If you did find a stuck/slow session, post to the configured UMMAYA feedback channel using the Slack MCP tool. Use ToolSearch to find \`slack_send_message\` if it's not already loaded.
45
45
 
46
46
  **Use a two-message structure** to keep the channel scannable:
47
47
 
48
- 1. **Top-level message** — one short line: hostname, Claude Code version, and a terse symptom (e.g. "session PID 12345 pegged at 100% CPU for 10min" or "git subprocess hung in D state"). No code blocks, no details.
48
+ 1. **Top-level message** — one short line: hostname, UMMAYA version, and a terse symptom (e.g. "session PID 12345 pegged at 100% CPU for 10min" or "git subprocess hung in D state"). No code blocks, no details.
49
49
  2. **Thread reply** — the full diagnostic dump. Pass the top-level message's \`ts\` as \`thread_ts\`. Include:
50
50
  - PID, CPU%, RSS, state, uptime, command line, child processes
51
51
  - Your diagnosis of what's likely wrong
52
52
  - Relevant debug log tail or \`sample\` output if you captured it
53
53
 
54
- If Slack MCP isn't available, format the report as a message the user can copy-paste into #claude-code-feedback (and let them know to thread the details themselves).
54
+ If Slack MCP isn't available, format the report as a message the user can copy-paste into the configured UMMAYA feedback channel (and let them know to thread the details themselves).
55
55
 
56
56
  ## Notes
57
57
  - Don't kill or signal any processes — this is diagnostic only.
@@ -66,7 +66,7 @@ export function registerStuckSkill(): void {
66
66
  registerBundledSkill({
67
67
  name: 'stuck',
68
68
  description:
69
- '[ANT-ONLY] Investigate frozen/stuck/slow Claude Code sessions on this machine and post a diagnostic report to #claude-code-feedback.',
69
+ '[ANT-ONLY] Investigate frozen/stuck/slow UMMAYA sessions on this machine and post a diagnostic report to the configured UMMAYA feedback channel.',
70
70
  userInvocable: true,
71
71
  async getPromptForCommand(args) {
72
72
  let prompt = STUCK_PROMPT
@@ -35,6 +35,10 @@ import type { DenialTrackingState } from '../utils/permissions/denialTracking.js
35
35
  import type { PermissionMode } from '../utils/permissions/PermissionMode.js'
36
36
  import { getInitialSettings } from '../utils/settings/settings.js'
37
37
  import type { SettingsJson } from '../utils/settings/types.js'
38
+ import {
39
+ getInitialReasoningModeSetting,
40
+ type ReasoningMode,
41
+ } from '../utils/kExaoneReasoning.js'
38
42
  import { shouldEnableThinkingByDefault } from '../utils/thinking.js'
39
43
  import type { Store } from './store.js'
40
44
 
@@ -425,6 +429,8 @@ export type AppState = DeepImmutable<{
425
429
  advisorModel?: string
426
430
  // Effort value
427
431
  effortValue?: EffortValue
432
+ // K-EXAONE/FriendliAI reasoning payload policy.
433
+ reasoningMode?: ReasoningMode
428
434
  // Set synchronously in launchUltraplan before the detached flow starts.
429
435
  // Prevents duplicate launches during the ~5s window before
430
436
  // ultraplanSessionUrl is set by teleportToRemote. Cleared by launchDetached
@@ -563,6 +569,7 @@ export function getDefaultAppState(): AppState {
563
569
  authVersion: 0,
564
570
  initialMessage: null,
565
571
  effortValue: undefined,
572
+ reasoningMode: getInitialReasoningModeSetting(),
566
573
  activeOverlays: new Set<string>(),
567
574
  fastMode: false,
568
575
  }