@prevalentware/opencode-goal-plugin 0.1.48 → 0.1.50

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.
package/src/tui.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import type { TuiCommand, TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"
2
- import type { Plugin as TuiPluginV2 } from "@opencode-ai/plugin-v2/tui"
3
- import type { SessionMessageInfo } from "@opencode-ai/client"
2
+ import type { Plugin as TuiPluginV2 } from "@opencode/plugin/tui"
3
+ import type { SessionMessageInfo } from "@opencode/client"
4
4
  import { createElement, insert, setProp } from "@opentui/solid"
5
5
  import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"
6
+ import type { GoalMessages } from "./i18n"
7
+ import { messagesFor, presentGoalLastStatus, presentGoalStatus, presentGoalStopReason, resolveLocale } from "./i18n"
6
8
 
7
9
  type GoalCheckpoint = {
8
10
  summary: string
@@ -194,8 +196,13 @@ function currentSessionID(api: TuiPluginApi) {
194
196
  return typeof sessionID === "string" ? sessionID : undefined
195
197
  }
196
198
 
197
- function toast(api: TuiPluginApi, message: string, variant: "info" | "success" | "warning" | "error" = "info") {
198
- api.ui.toast({ title: "Goal", message, variant, duration: 2500 })
199
+ function toast(
200
+ api: TuiPluginApi,
201
+ messages: GoalMessages,
202
+ message: string,
203
+ variant: "info" | "success" | "warning" | "error" = "info",
204
+ ) {
205
+ api.ui.toast({ title: messages.tui.title, message, variant, duration: 2500 })
199
206
  }
200
207
 
201
208
  async function sendGoalPrompt(api: TuiPluginApi, sessionID: string, text: string) {
@@ -205,27 +212,35 @@ async function sendGoalPrompt(api: TuiPluginApi, sessionID: string, text: string
205
212
  })
206
213
  }
207
214
 
208
- function refreshGoalPrompt() {
209
- return "Call get_goal for this session and report the current goal state briefly."
215
+ function refreshGoalPrompt(messages: GoalMessages) {
216
+ return messages.tui.refreshPrompt
210
217
  }
211
218
 
212
- function clearGoalPrompt() {
213
- return "Clear the current session goal by calling clear_goal. Report whether a goal was cleared."
219
+ function clearGoalPrompt(messages: GoalMessages) {
220
+ return messages.tui.clearPrompt
214
221
  }
215
222
 
216
- function pauseGoalPrompt() {
217
- return 'Pause the current session goal by calling update_goal_status with status "paused". Report the result briefly.'
223
+ function pauseGoalPrompt(messages: GoalMessages) {
224
+ return messages.tui.pausePrompt
218
225
  }
219
226
 
220
- function resumeGoalPrompt() {
221
- return 'Resume the current session goal by calling update_goal_status with status "active", then continue working toward it.'
227
+ function resumeGoalPrompt(messages: GoalMessages) {
228
+ return messages.tui.resumePrompt
222
229
  }
223
230
 
224
- function historyGoalPrompt() {
225
- return "Call get_goal_history for this session and report the current goal history briefly."
231
+ function historyGoalPrompt(messages: GoalMessages) {
232
+ return messages.tui.historyPrompt
226
233
  }
227
234
 
228
- function actionOption(api: TuiPluginApi, sessionID: string, title: string, value: string, description: string, prompt: string) {
235
+ function actionOption(
236
+ api: TuiPluginApi,
237
+ messages: GoalMessages,
238
+ sessionID: string,
239
+ title: string,
240
+ value: string,
241
+ description: string,
242
+ prompt: string,
243
+ ) {
229
244
  return {
230
245
  title,
231
246
  value,
@@ -233,25 +248,75 @@ function actionOption(api: TuiPluginApi, sessionID: string, title: string, value
233
248
  onSelect: () => {
234
249
  void sendGoalPrompt(api, sessionID, prompt)
235
250
  .then(() => api.ui.dialog.clear())
236
- .catch((error) => toast(api, error instanceof Error ? error.message : String(error), "error"))
251
+ .catch((error) => toast(api, messages, error instanceof Error ? error.message : String(error), "error"))
237
252
  },
238
253
  }
239
254
  }
240
255
 
241
- function showSummary(api: TuiPluginApi, sessionID: string, goal: GoalSnapshot | null) {
256
+ function showSummary(
257
+ api: TuiPluginApi,
258
+ messages: GoalMessages,
259
+ locale: ReturnType<typeof resolveLocale>,
260
+ sessionID: string,
261
+ goal: GoalSnapshot | null,
262
+ ) {
242
263
  const DialogSelect = api.ui.DialogSelect
243
264
  const options = [
244
- actionOption(api, sessionID, "Refresh", "refresh", "Ask the agent to read the current goal state", refreshGoalPrompt()),
265
+ actionOption(
266
+ api,
267
+ messages,
268
+ sessionID,
269
+ messages.tui.refresh,
270
+ "refresh",
271
+ messages.tui.refreshDescription,
272
+ refreshGoalPrompt(messages),
273
+ ),
245
274
  ...(goal
246
275
  ? [
247
- actionOption(api, sessionID, "History", "history", "Ask the agent to show lifecycle history", historyGoalPrompt()),
276
+ actionOption(
277
+ api,
278
+ messages,
279
+ sessionID,
280
+ messages.tui.history,
281
+ "history",
282
+ messages.tui.historyDescription,
283
+ historyGoalPrompt(messages),
284
+ ),
248
285
  ...(goal.status === "active"
249
- ? [actionOption(api, sessionID, "Pause", "pause", "Pause auto-continuation without clearing", pauseGoalPrompt())]
286
+ ? [
287
+ actionOption(
288
+ api,
289
+ messages,
290
+ sessionID,
291
+ messages.tui.pause,
292
+ "pause",
293
+ messages.tui.pauseDescription,
294
+ pauseGoalPrompt(messages),
295
+ ),
296
+ ]
250
297
  : []),
251
298
  ...(goal.status === "paused" || goal.status === "budgetLimited" || goal.status === "usageLimited"
252
- ? [actionOption(api, sessionID, "Resume", "resume", "Resume the goal and continue", resumeGoalPrompt())]
299
+ ? [
300
+ actionOption(
301
+ api,
302
+ messages,
303
+ sessionID,
304
+ messages.tui.resume,
305
+ "resume",
306
+ messages.tui.resumeDescription,
307
+ resumeGoalPrompt(messages),
308
+ ),
309
+ ]
253
310
  : []),
254
- actionOption(api, sessionID, "Clear", "clear", "Ask the agent to clear this session goal", clearGoalPrompt()),
311
+ actionOption(
312
+ api,
313
+ messages,
314
+ sessionID,
315
+ messages.tui.clear,
316
+ "clear",
317
+ messages.tui.clearDescription,
318
+ clearGoalPrompt(messages),
319
+ ),
255
320
  ]
256
321
  : []),
257
322
  ]
@@ -259,8 +324,8 @@ function showSummary(api: TuiPluginApi, sessionID: string, goal: GoalSnapshot |
259
324
  api.ui.dialog.setSize("large")
260
325
  api.ui.dialog.replace(() =>
261
326
  DialogSelect({
262
- title: "Goal",
263
- placeholder: formatGoal(goal),
327
+ title: messages.tui.title,
328
+ placeholder: formatGoal(goal, messages, locale),
264
329
  options,
265
330
  onSelect(option) {
266
331
  option.onSelect?.()
@@ -269,9 +334,9 @@ function showSummary(api: TuiPluginApi, sessionID: string, goal: GoalSnapshot |
269
334
  )
270
335
  }
271
336
 
272
- function sessionIDOrToast(api: TuiPluginApi) {
337
+ function sessionIDOrToast(api: TuiPluginApi, messages: GoalMessages) {
273
338
  const sessionID = currentSessionID(api)
274
- if (!sessionID) toast(api, "Open a session before viewing goal state.", "warning")
339
+ if (!sessionID) toast(api, messages, messages.tui.openSession, "warning")
275
340
  return sessionID
276
341
  }
277
342
 
@@ -384,34 +449,37 @@ function goalFromSession(api: TuiPluginApi, sessionID: string) {
384
449
  return goalStateFromSession(api, sessionID).goal
385
450
  }
386
451
 
387
- function formatGoal(goal: GoalSnapshot | null) {
388
- if (!goal) return "No recent goal state found in this session."
452
+ export function formatGoal(goal: GoalSnapshot | null, messages: GoalMessages, locale: ReturnType<typeof resolveLocale>) {
453
+ if (!goal) return messages.tui.noGoal
389
454
  const lines = [
390
- `Objective: ${goal.objective}`,
391
- `Status: ${goal.status}`,
392
- `Time used: ${formatDuration(goal.timeUsedSeconds)}`,
393
- `Tokens: ${goal.tokensUsed}${goal.tokenBudget == null ? "" : `/${goal.tokenBudget}`}`,
394
- `Auto-continues: ${goal.autoTurns}${goal.maxAutoTurns == null ? "" : `/${goal.maxAutoTurns}`}`,
455
+ `${messages.tui.objective}: ${goal.objective}`,
456
+ `${messages.tui.status}: ${presentGoalStatus(goal.status, locale)}`,
457
+ `${messages.tui.timeUsed}: ${formatDuration(goal.timeUsedSeconds)}`,
458
+ `${messages.tui.tokens}: ${goal.tokensUsed}${goal.tokenBudget == null ? "" : `/${goal.tokenBudget}`}`,
459
+ `${messages.tui.autoContinues}: ${goal.autoTurns}${goal.maxAutoTurns == null ? "" : `/${goal.maxAutoTurns}`}`,
395
460
  ]
396
- if (goal.remainingTokens != null) lines.push(`Tokens remaining: ${goal.remainingTokens}`)
397
- if (goal.maxDurationSeconds != null) lines.push(`Duration limit: ${formatDuration(goal.maxDurationSeconds)}`)
398
- if (goal.noProgressTurns > 0) lines.push(`No-progress turns: ${goal.noProgressTurns}`)
399
- if (goal.lastCheckpoint) lines.push(`Latest checkpoint: ${goal.lastCheckpoint.summary}`)
400
- if (goal.stopReason) lines.push(`Stop reason: ${goal.stopReason}`)
401
- if (goal.lastStatus) lines.push(`Last status: ${goal.lastStatus}`)
402
- if (goal.completionEvidence) lines.push(`Completion evidence: ${goal.completionEvidence}`)
403
- if (goal.blocker) lines.push(`Blocker: ${goal.blocker}`)
461
+ if (goal.remainingTokens != null) lines.push(`${messages.tui.tokensRemaining}: ${goal.remainingTokens}`)
462
+ if (goal.maxDurationSeconds != null) lines.push(`${messages.tui.durationLimit}: ${formatDuration(goal.maxDurationSeconds)}`)
463
+ if (goal.noProgressTurns > 0) lines.push(`${messages.tui.noProgressTurns}: ${goal.noProgressTurns}`)
464
+ if (goal.lastCheckpoint) lines.push(`${messages.tui.latestCheckpoint}: ${goal.lastCheckpoint.summary}`)
465
+ if (goal.stopReason) lines.push(`${messages.tui.stopReason}: ${presentGoalStopReason(goal.stopReason, locale)}`)
466
+ if (goal.lastStatus) lines.push(`${messages.tui.lastStatus}: ${presentGoalLastStatus(goal.lastStatus, locale)}`)
467
+ if (goal.completionEvidence) lines.push(`${messages.tui.completionEvidence}: ${goal.completionEvidence}`)
468
+ if (goal.blocker) lines.push(`${messages.tui.blocker}: ${presentGoalLastStatus(goal.blocker, locale)}`)
404
469
  return lines.join("\n")
405
470
  }
406
471
 
407
- function GoalSidebar(api: TuiPluginApi, sessionID: string) {
472
+ function GoalSidebar(api: TuiPluginApi, messages: GoalMessages, locale: ReturnType<typeof resolveLocale>, sessionID: string) {
408
473
  const theme = api.theme.current
409
474
  const state = goalStateFromSession(api, sessionID)
410
475
  const goal = state.goal
411
476
  if (!goal) return null
412
477
  if (goal.status === "complete" || goal.status === "unmet") {
413
478
  const elapsed = liveTimeUsedSeconds(goal)
414
- return text({ fg: goal.status === "complete" ? theme.primary : theme.textMuted }, [`${goal.status === "complete" ? "Goal achieved" : "Goal unmet"} (${formatDurationBadge(elapsed)})`])
479
+ const label = goal.status === "complete" ? messages.tui.achieved : messages.tui.unmet
480
+ return text({ fg: goal.status === "complete" ? theme.primary : theme.textMuted }, [
481
+ `${label} (${formatDurationBadge(elapsed)})`,
482
+ ])
415
483
  }
416
484
  const [nowSeconds, setNowSeconds] = createSignal(currentEpochSeconds())
417
485
  if (goal.status === "active") {
@@ -419,14 +487,22 @@ function GoalSidebar(api: TuiPluginApi, sessionID: string) {
419
487
  onCleanup(() => clearInterval(timer))
420
488
  }
421
489
  return box({}, [
422
- text({ fg: theme.text }, ["Goal"]),
423
- text({ fg: theme.textMuted }, [`Status: ${goal.status}`]),
424
- text({ fg: theme.textMuted }, [() => `Time: ${formatDuration(liveTimeUsedSeconds(goal, nowSeconds()))}`]),
425
- text({ fg: theme.textMuted }, [`Tokens: ${goal.tokensUsed}${goal.tokenBudget == null ? "" : `/${goal.tokenBudget}`}`]),
426
- text({ fg: theme.textMuted }, [`Auto-continues: ${goal.autoTurns}${goal.maxAutoTurns == null ? "" : `/${goal.maxAutoTurns}`}`]),
427
- ...(goal.lastCheckpoint ? [text({ fg: theme.textMuted }, [`Checkpoint: ${goal.lastCheckpoint.summary}`])] : []),
428
- ...(goal.stopReason ? [text({ fg: theme.textMuted }, [`Stop: ${goal.stopReason}`])] : []),
429
- ...(goal.lastStatus ? [text({ fg: theme.textMuted }, [goal.lastStatus])] : []),
490
+ text({ fg: theme.text }, [messages.tui.title]),
491
+ text({ fg: theme.textMuted }, [`${messages.tui.status}: ${presentGoalStatus(goal.status, locale)}`]),
492
+ text({ fg: theme.textMuted }, [() => `${messages.tui.time}: ${formatDuration(liveTimeUsedSeconds(goal, nowSeconds()))}`]),
493
+ text({ fg: theme.textMuted }, [
494
+ `${messages.tui.tokens}: ${goal.tokensUsed}${goal.tokenBudget == null ? "" : `/${goal.tokenBudget}`}`,
495
+ ]),
496
+ text({ fg: theme.textMuted }, [
497
+ `${messages.tui.autoContinues}: ${goal.autoTurns}${goal.maxAutoTurns == null ? "" : `/${goal.maxAutoTurns}`}`,
498
+ ]),
499
+ ...(goal.lastCheckpoint
500
+ ? [text({ fg: theme.textMuted }, [`${messages.tui.checkpoint}: ${goal.lastCheckpoint.summary}`])]
501
+ : []),
502
+ ...(goal.stopReason
503
+ ? [text({ fg: theme.textMuted }, [`${messages.tui.stop}: ${presentGoalStopReason(goal.stopReason, locale)}`])]
504
+ : []),
505
+ ...(goal.lastStatus ? [text({ fg: theme.textMuted }, [presentGoalLastStatus(goal.lastStatus, locale)])] : []),
430
506
  text({ fg: theme.textMuted }, [goal.objective]),
431
507
  ])
432
508
  }
@@ -452,25 +528,27 @@ function registerGoalCommand(api: TuiPluginApi, command: TuiCommand) {
452
528
  api.command?.register(() => [command])
453
529
  }
454
530
 
455
- const tui: TuiPlugin = async (api) => {
531
+ const tui: TuiPlugin = async (api, options) => {
532
+ const locale = resolveLocale(typeof options?.locale === "string" ? options.locale : undefined)
533
+ const messages = messagesFor(locale)
456
534
  api.slots.register({
457
535
  order: 125,
458
536
  slots: {
459
537
  sidebar_content(_ctx, props) {
460
- return GoalSidebar(api, props.session_id)
538
+ return GoalSidebar(api, messages, locale, props.session_id)
461
539
  },
462
540
  },
463
541
  })
464
542
 
465
543
  registerGoalCommand(api, {
466
- title: "Goal",
544
+ title: messages.tui.title,
467
545
  value: "goal.show",
468
- category: "Goal",
469
- description: "View, pause, resume, or clear the long-running session goal",
546
+ category: messages.tui.title,
547
+ description: messages.tui.commandDescription,
470
548
  onSelect: () => {
471
- const sessionID = sessionIDOrToast(api)
549
+ const sessionID = sessionIDOrToast(api, messages)
472
550
  if (!sessionID) return
473
- showSummary(api, sessionID, goalFromSession(api, sessionID))
551
+ showSummary(api, messages, locale, sessionID, goalFromSession(api, sessionID))
474
552
  },
475
553
  })
476
554
  }
@@ -516,43 +594,63 @@ function currentSessionIDV2(api: TuiPluginV2.Context) {
516
594
  return route.sessionID
517
595
  }
518
596
 
519
- function toastV2(api: TuiPluginV2.Context, message: string, variant: "info" | "success" | "warning" | "error" = "info") {
520
- api.ui.toast.show({ title: "Goal", message, variant, duration: 2500 })
597
+ function toastV2(
598
+ api: TuiPluginV2.Context,
599
+ messages: GoalMessages,
600
+ message: string,
601
+ variant: "info" | "success" | "warning" | "error" = "info",
602
+ ) {
603
+ api.ui.toast.show({ title: messages.tui.title, message, variant, duration: 2500 })
521
604
  }
522
605
 
523
- async function showSummaryV2(api: TuiPluginV2.Context, sessionID: string, goal: GoalSnapshot | null) {
606
+ async function showSummaryV2(
607
+ api: TuiPluginV2.Context,
608
+ messages: GoalMessages,
609
+ locale: ReturnType<typeof resolveLocale>,
610
+ sessionID: string,
611
+ goal: GoalSnapshot | null,
612
+ ) {
524
613
  const options = [
525
- { title: "Refresh", value: "refresh", description: "Ask the agent to read the current goal state" },
614
+ { title: messages.tui.refresh, value: "refresh", description: messages.tui.refreshDescription },
526
615
  ...(goal
527
616
  ? [
528
- { title: "History", value: "history", description: "Ask the agent to show lifecycle history" },
617
+ { title: messages.tui.history, value: "history", description: messages.tui.historyDescription },
529
618
  ...(goal.status === "active"
530
- ? [{ title: "Pause", value: "pause", description: "Pause auto-continuation without clearing" }]
619
+ ? [{ title: messages.tui.pause, value: "pause", description: messages.tui.pauseDescription }]
531
620
  : []),
532
621
  ...(goal.status === "paused" || goal.status === "budgetLimited" || goal.status === "usageLimited"
533
- ? [{ title: "Resume", value: "resume", description: "Resume the goal and continue" }]
622
+ ? [{ title: messages.tui.resume, value: "resume", description: messages.tui.resumeDescription }]
534
623
  : []),
535
- { title: "Clear", value: "clear", description: "Ask the agent to clear this session goal" },
624
+ { title: messages.tui.clear, value: "clear", description: messages.tui.clearDescription },
536
625
  ]
537
626
  : []),
538
627
  ]
539
628
  api.ui.dialog.set({ size: "large" })
540
- const selected = await api.ui.dialog.select({ title: "Goal", placeholder: formatGoal(goal), options })
541
- const prompt = selected === "refresh" ? refreshGoalPrompt()
542
- : selected === "history" ? historyGoalPrompt()
543
- : selected === "pause" ? pauseGoalPrompt()
544
- : selected === "resume" ? resumeGoalPrompt()
545
- : selected === "clear" ? clearGoalPrompt()
629
+ const selected = await api.ui.dialog.select({
630
+ title: messages.tui.title,
631
+ placeholder: formatGoal(goal, messages, locale),
632
+ options,
633
+ })
634
+ const prompt = selected === "refresh" ? refreshGoalPrompt(messages)
635
+ : selected === "history" ? historyGoalPrompt(messages)
636
+ : selected === "pause" ? pauseGoalPrompt(messages)
637
+ : selected === "resume" ? resumeGoalPrompt(messages)
638
+ : selected === "clear" ? clearGoalPrompt(messages)
546
639
  : undefined
547
640
  if (!prompt) return
548
641
  try {
549
642
  await api.client.session.prompt({ sessionID, text: prompt })
550
643
  } catch (error) {
551
- toastV2(api, error instanceof Error ? error.message : String(error), "error")
644
+ toastV2(api, messages, error instanceof Error ? error.message : String(error), "error")
552
645
  }
553
646
  }
554
647
 
555
- function GoalSidebarV2(api: TuiPluginV2.Context, sessionID: string) {
648
+ function GoalSidebarV2(
649
+ api: TuiPluginV2.Context,
650
+ messages: GoalMessages,
651
+ locale: ReturnType<typeof resolveLocale>,
652
+ sessionID: string,
653
+ ) {
556
654
  const colors = goalColorsV2(api.theme)
557
655
  const [cache, setCache] = api.storage.memory<{ goal: GoalSnapshot | null }>(`goal-mode.v2.${sessionID}`, {
558
656
  initial: { goal: null },
@@ -579,40 +677,58 @@ function GoalSidebarV2(api: TuiPluginV2.Context, sessionID: string) {
579
677
  if (snapshot.status === "complete" || snapshot.status === "unmet") {
580
678
  const elapsed = liveTimeUsedSeconds(snapshot)
581
679
  return text({ fg: snapshot.status === "complete" ? colors.achieved : colors.muted }, [
582
- `${snapshot.status === "complete" ? "Goal achieved" : "Goal unmet"} (${formatDurationBadge(elapsed)})`,
680
+ `${snapshot.status === "complete" ? messages.tui.achieved : messages.tui.unmet} (${formatDurationBadge(elapsed)})`,
583
681
  ])
584
682
  }
585
683
  return box({}, [
586
- text({ fg: colors.text }, ["Goal"]),
587
- text({ fg: colors.muted }, [`Status: ${snapshot.status}`]),
588
- text({ fg: colors.muted }, [`Time: ${formatDuration(liveTimeUsedSeconds(snapshot, nowSeconds()))}`]),
589
- text({ fg: colors.muted }, [`Tokens: ${snapshot.tokensUsed}${snapshot.tokenBudget == null ? "" : `/${snapshot.tokenBudget}`}`]),
590
- text({ fg: colors.muted }, [`Auto-continues: ${snapshot.autoTurns}${snapshot.maxAutoTurns == null ? "" : `/${snapshot.maxAutoTurns}`}`]),
591
- ...(snapshot.lastCheckpoint ? [text({ fg: colors.muted }, [`Checkpoint: ${snapshot.lastCheckpoint.summary}`])] : []),
592
- ...(snapshot.stopReason ? [text({ fg: colors.muted }, [`Stop: ${snapshot.stopReason}`])] : []),
593
- ...(snapshot.lastStatus ? [text({ fg: colors.muted }, [snapshot.lastStatus])] : []),
684
+ text({ fg: colors.text }, [messages.tui.title]),
685
+ text({ fg: colors.muted }, [`${messages.tui.status}: ${presentGoalStatus(snapshot.status, locale)}`]),
686
+ text({ fg: colors.muted }, [`${messages.tui.time}: ${formatDuration(liveTimeUsedSeconds(snapshot, nowSeconds()))}`]),
687
+ text({ fg: colors.muted }, [
688
+ `${messages.tui.tokens}: ${snapshot.tokensUsed}${snapshot.tokenBudget == null ? "" : `/${snapshot.tokenBudget}`}`,
689
+ ]),
690
+ text({ fg: colors.muted }, [
691
+ `${messages.tui.autoContinues}: ${snapshot.autoTurns}${
692
+ snapshot.maxAutoTurns == null ? "" : `/${snapshot.maxAutoTurns}`
693
+ }`,
694
+ ]),
695
+ ...(snapshot.lastCheckpoint
696
+ ? [text({ fg: colors.muted }, [`${messages.tui.checkpoint}: ${snapshot.lastCheckpoint.summary}`])]
697
+ : []),
698
+ ...(snapshot.stopReason
699
+ ? [
700
+ text(
701
+ { fg: colors.muted },
702
+ [`${messages.tui.stop}: ${presentGoalStopReason(snapshot.stopReason, locale)}`],
703
+ ),
704
+ ]
705
+ : []),
706
+ ...(snapshot.lastStatus
707
+ ? [text({ fg: colors.muted }, [presentGoalLastStatus(snapshot.lastStatus, locale)])]
708
+ : []),
594
709
  text({ fg: colors.muted }, [snapshot.objective]),
595
710
  ])
596
711
  }])
597
712
  }
598
713
 
599
- function GoalKeymapLayerV2(api: TuiPluginV2.Context) {
714
+ function GoalKeymapLayerV2(api: TuiPluginV2.Context, messages: GoalMessages, locale: ReturnType<typeof resolveLocale>) {
600
715
  api.keymap.layer(() => ({
601
716
  mode: "global",
602
717
  commands: [
603
718
  {
604
719
  id: "goal.show",
605
- title: "Goal",
606
- description: "View, pause, resume, or clear the long-running session goal",
607
- group: "Goal",
720
+ title: messages.tui.title,
721
+ description: messages.tui.commandDescription,
722
+ group: messages.tui.title,
608
723
  palette: true,
609
724
  run: () => {
610
725
  const sessionID = currentSessionIDV2(api)
611
726
  if (!sessionID) {
612
- toastV2(api, "Open a session before viewing goal state.", "warning")
727
+ toastV2(api, messages, messages.tui.openSession, "warning")
613
728
  return
614
729
  }
615
- void showSummaryV2(api, sessionID, goalFromV2Messages(api.data.session.message.list(sessionID)) ?? null)
730
+ const goal = goalFromV2Messages(api.data.session.message.list(sessionID)) ?? null
731
+ void showSummaryV2(api, messages, locale, sessionID, goal)
616
732
  },
617
733
  },
618
734
  ],
@@ -629,8 +745,12 @@ function GoalKeymapLayerV2(api: TuiPluginV2.Context) {
629
745
  * needs to dispose the two `ui.slot` registrations.
630
746
  */
631
747
  export function setupTuiV2(context: TuiPluginV2.Context): TuiPluginV2.Cleanup {
632
- const offSidebar = registerSlotV2(context, "sidebar.content", (props) => GoalSidebarV2(context, props.sessionID))
633
- const offApp = registerSlotV2(context, "app", () => GoalKeymapLayerV2(context))
748
+ const locale = resolveLocale(typeof context.options?.locale === "string" ? context.options.locale : undefined)
749
+ const messages = messagesFor(locale)
750
+ const offSidebar = registerSlotV2(context, "sidebar.content", (props) =>
751
+ GoalSidebarV2(context, messages, locale, props.sessionID),
752
+ )
753
+ const offApp = registerSlotV2(context, "app", () => GoalKeymapLayerV2(context, messages, locale))
634
754
  return () => {
635
755
  offSidebar()
636
756
  offApp()