talon-agent 5.0.0 → 5.0.1

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 (127) hide show
  1. package/package.json +1 -1
  2. package/src/app.ts +3 -3
  3. package/src/backend/claude-sdk/factory.ts +21 -41
  4. package/src/backend/claude-sdk/host/in-process.ts +147 -0
  5. package/src/backend/codex/mcp-config.ts +1 -1
  6. package/src/backend/openai-agents/mcp-pool.ts +1 -1
  7. package/src/backend/runtime/index.ts +2 -2
  8. package/src/backend/runtime/turn/delivery.ts +1 -1
  9. package/src/backend/runtime/turn/turn-phases.ts +1 -1
  10. package/src/bootstrap.ts +2 -2
  11. package/src/cli.ts +5 -2
  12. package/src/core/agent-runtime/README.md +15 -0
  13. package/src/core/agent-runtime/agent-host.ts +387 -0
  14. package/src/core/background/cron/scheduler.ts +1 -1
  15. package/src/core/background/triggers/command.ts +1 -1
  16. package/src/{util → core/config}/harden.ts +1 -1
  17. package/src/core/config/index.ts +1 -1
  18. package/src/core/daemon/pidfile.ts +1 -1
  19. package/src/core/daemon/resource-sampler.ts +2 -2
  20. package/src/{util → core/daemon}/respawn.ts +1 -1
  21. package/src/core/doctor/index.ts +1 -1
  22. package/src/core/engine/gateway-actions/fetch-url.ts +1 -1
  23. package/src/core/engine/gateway-actions/index.ts +1 -1
  24. package/src/core/engine/gateway-actions/memory.ts +2 -2
  25. package/src/core/engine/gateway-actions/native/exec-background.ts +129 -0
  26. package/src/core/engine/gateway-actions/native/exec-remote.ts +85 -0
  27. package/src/core/engine/gateway-actions/native/exec.ts +182 -0
  28. package/src/core/engine/gateway-actions/native/index.ts +46 -0
  29. package/src/core/engine/gateway-actions/native/params.ts +46 -0
  30. package/src/core/engine/gateway-actions/native/read.ts +182 -0
  31. package/src/core/engine/gateway-actions/native/results.ts +34 -0
  32. package/src/core/engine/gateway-actions/native/search.ts +212 -0
  33. package/src/core/engine/gateway-actions/native/shell.ts +39 -0
  34. package/src/core/engine/gateway-actions/native/teleport.ts +55 -0
  35. package/src/core/engine/gateway-actions/native/write.ts +170 -0
  36. package/src/core/frontend-runtime/builtins.ts +2 -2
  37. package/src/core/mcp-hub/index.ts +1 -1
  38. package/src/core/mcp-hub/talon-server.ts +1 -1
  39. package/src/core/memory/taps.ts +1 -1
  40. package/src/core/plugin/mcp.ts +1 -1
  41. package/src/core/scripts/lua.ts +1 -1
  42. package/src/core/tools/{chat.ts → chat/chat.ts} +1 -1
  43. package/src/core/tools/{cross-send.ts → chat/cross-send.ts} +1 -1
  44. package/src/core/tools/{history.ts → chat/history.ts} +2 -2
  45. package/src/core/tools/{media.ts → chat/media.ts} +1 -1
  46. package/src/core/tools/{members.ts → chat/members.ts} +2 -2
  47. package/src/core/tools/{messaging.ts → chat/messaging.ts} +2 -2
  48. package/src/core/tools/{moderation.ts → chat/moderation.ts} +2 -2
  49. package/src/core/tools/{stickers.ts → chat/stickers.ts} +2 -2
  50. package/src/core/tools/{whatsapp.ts → chat/whatsapp.ts} +1 -1
  51. package/src/core/tools/{memory.ts → content/memory.ts} +2 -2
  52. package/src/core/tools/{web.ts → content/web.ts} +1 -1
  53. package/src/core/tools/index.ts +20 -20
  54. package/src/core/tools/{admin.ts → ops/admin.ts} +1 -1
  55. package/src/core/tools/{bridge.ts → ops/bridge.ts} +2 -2
  56. package/src/core/tools/{goals.ts → ops/goals.ts} +2 -2
  57. package/src/core/tools/{mesh.ts → ops/mesh.ts} +1 -1
  58. package/src/core/tools/{models.ts → ops/models.ts} +1 -1
  59. package/src/core/tools/{native.ts → ops/native.ts} +1 -1
  60. package/src/core/tools/{scheduling.ts → ops/scheduling.ts} +1 -1
  61. package/src/core/tools/{scripts.ts → ops/scripts.ts} +1 -1
  62. package/src/core/tools/{skills.ts → ops/skills.ts} +1 -1
  63. package/src/core/tools/{triggers.ts → ops/triggers.ts} +1 -1
  64. package/src/{util → core/vfs}/workspace.ts +2 -2
  65. package/src/frontend/discord/callbacks/components/effort.ts +1 -1
  66. package/src/frontend/discord/callbacks/components/index.ts +1 -1
  67. package/src/frontend/discord/callbacks/components/settings.ts +1 -1
  68. package/src/frontend/discord/commands/admin.ts +1 -1
  69. package/src/frontend/discord/commands/info.ts +1 -1
  70. package/src/frontend/discord/commands/interaction.ts +1 -1
  71. package/src/frontend/discord/commands/session.ts +3 -3
  72. package/src/frontend/discord/commands/settings.ts +1 -1
  73. package/src/frontend/discord/handlers/messages.ts +1 -1
  74. package/src/frontend/discord/handlers/state.ts +1 -1
  75. package/src/frontend/discord/middleware.ts +1 -1
  76. package/src/frontend/discord/ready.ts +1 -1
  77. package/src/frontend/discord/render.ts +41 -259
  78. package/src/frontend/native/chats/chats.ts +1 -1
  79. package/src/frontend/native/surface/models.ts +1 -1
  80. package/src/frontend/native/turn/context.ts +1 -1
  81. package/src/frontend/native/turn/emit.ts +1 -1
  82. package/src/frontend/{shared → presentation}/format.ts +4 -3
  83. package/src/frontend/{shared → presentation}/reasoning-levels.ts +12 -0
  84. package/src/frontend/presentation/reports.ts +493 -0
  85. package/src/frontend/{shared → presentation}/session-status.ts +1 -1
  86. package/src/frontend/teams/commands.ts +2 -2
  87. package/src/frontend/teams/turn.ts +1 -1
  88. package/src/frontend/telegram/admin/health.ts +1 -1
  89. package/src/frontend/telegram/admin/sessions.ts +1 -1
  90. package/src/frontend/telegram/callbacks/effort.ts +2 -2
  91. package/src/frontend/telegram/callbacks/metrics.ts +1 -1
  92. package/src/frontend/telegram/callbacks/model/views.ts +1 -1
  93. package/src/frontend/telegram/callbacks/query.ts +1 -1
  94. package/src/frontend/telegram/callbacks/settings.ts +2 -2
  95. package/src/frontend/telegram/commands/admin.ts +4 -4
  96. package/src/frontend/telegram/commands/info.ts +2 -2
  97. package/src/frontend/telegram/commands/session.ts +4 -4
  98. package/src/frontend/telegram/commands/settings.ts +4 -2
  99. package/src/frontend/telegram/handlers/state.ts +1 -1
  100. package/src/frontend/telegram/model-menu.ts +1 -1
  101. package/src/frontend/telegram/render/html.ts +30 -0
  102. package/src/frontend/telegram/{helpers → render}/menu.ts +46 -19
  103. package/src/frontend/telegram/render/reports.ts +182 -0
  104. package/src/frontend/terminal/builtins/context.ts +2 -2
  105. package/src/frontend/terminal/builtins/session.ts +1 -1
  106. package/src/frontend/terminal/builtins/status.ts +2 -2
  107. package/src/frontend/terminal/index.ts +2 -2
  108. package/src/frontend/terminal/renderer.ts +1 -1
  109. package/src/frontend/whatsapp/commands.ts +5 -5
  110. package/src/frontend/whatsapp/registry.ts +1 -1
  111. package/src/index.ts +5 -2
  112. package/src/util/runtime.ts +1 -1
  113. package/src/core/engine/gateway-actions/native.ts +0 -1035
  114. package/src/frontend/telegram/helpers/diagnostics.ts +0 -428
  115. package/src/frontend/telegram/helpers/format.ts +0 -42
  116. package/src/frontend/telegram/helpers/index.ts +0 -13
  117. package/src/util/cleanup-registry.ts +0 -36
  118. /package/src/{util → core/daemon}/boot-timer.ts +0 -0
  119. /package/src/{util → core/frontend-runtime}/chat-id.ts +0 -0
  120. /package/src/{util/mcp-launcher.ts → core/mcp-hub/launcher.ts} +0 -0
  121. /package/src/{util → core/tools/content}/web-content.ts +0 -0
  122. /package/src/core/tools/{mcp-env.ts → ops/mcp-env.ts} +0 -0
  123. /package/src/{util → core/weaver}/session-name.ts +0 -0
  124. /package/src/frontend/{shared → presentation}/access.ts +0 -0
  125. /package/src/frontend/{shared → presentation}/model-commands.ts +0 -0
  126. /package/src/frontend/{shared → presentation}/plan-usage-report.ts +0 -0
  127. /package/src/frontend/{shared → presentation}/status-context.ts +0 -0
@@ -20,14 +20,14 @@ import {
20
20
  renderSettingsText,
21
21
  renderSettingsKeyboard,
22
22
  type SettingsButton,
23
- } from "../helpers/index.js";
23
+ } from "../render/menu.js";
24
24
  import { resolveBackendForChat } from "../model-menu.js";
25
25
  import { resolveActiveModelForChat } from "../../../core/models/active-model.js";
26
26
  import {
27
27
  displayReasoningEffort,
28
28
  getActiveReasoningLevels,
29
29
  supportsReasoningLevel,
30
- } from "../../shared/reasoning-levels.js";
30
+ } from "../../presentation/reasoning-levels.js";
31
31
  import { answerCallbackQuerySafe, type CallbackDeps } from "./query.js";
32
32
 
33
33
  export async function handleSettingsCallback(
@@ -8,7 +8,7 @@
8
8
 
9
9
  import type { Bot } from "grammy";
10
10
  import type { TalonConfig } from "../../../core/config/index.js";
11
- import { respawnSelf } from "../../../util/respawn.js";
11
+ import { respawnSelf } from "../../../core/daemon/respawn.js";
12
12
  import { isStaleCommand } from "../stale-command.js";
13
13
  import {
14
14
  getRepoRoot,
@@ -17,14 +17,14 @@ import {
17
17
  import { forceDream } from "../../../core/background/dream/index.js";
18
18
  import { escapeHtml } from "../formatting.js";
19
19
  import { closestMatch } from "../../../native/strsim.js";
20
+ import { formatDuration } from "../../presentation/format.js";
20
21
  import {
21
- formatDuration,
22
22
  renderDoctorMessage,
23
23
  renderMetricsKeyboard,
24
24
  renderMetricsPanel,
25
25
  renderUsageMessage,
26
- } from "../helpers/index.js";
27
- import { collectPlanUsage } from "../../shared/plan-usage-report.js";
26
+ } from "../render/reports.js";
27
+ import { collectPlanUsage } from "../../presentation/plan-usage-report.js";
28
28
  import { collectDoctorReport } from "../../../core/doctor/index.js";
29
29
  import { handleAdminCommand } from "../admin.js";
30
30
  import { getTodayMetrics } from "../../../storage/metrics.js";
@@ -5,12 +5,12 @@
5
5
  import type { Bot, Context } from "grammy";
6
6
  import { isUserClientReady } from "../userbot.js";
7
7
  import { escapeHtml } from "../formatting.js";
8
+ import { formatDuration } from "../../presentation/format.js";
8
9
  import {
9
- formatDuration,
10
10
  renderMeshPairLink,
11
11
  renderMeshReport,
12
12
  type MeshReachability,
13
- } from "../helpers/index.js";
13
+ } from "../render/reports.js";
14
14
  import { isAuthorizedAdmin } from "./state.js";
15
15
  import { getLoadedPlugins } from "../../../core/plugin/index.js";
16
16
  import { getMeshService } from "../../../core/mesh/index.js";
@@ -2,7 +2,7 @@
2
2
  * Session commands — /reset and /status.
3
3
  *
4
4
  * Data gathering and the reset sequence live in
5
- * frontend/shared/session-status.ts; this file only renders Telegram HTML.
5
+ * frontend/presentation/session-status.ts; this file only renders Telegram HTML.
6
6
  */
7
7
 
8
8
  import type { Bot } from "grammy";
@@ -13,17 +13,17 @@ import {
13
13
  formatTokenCount,
14
14
  formatBytes,
15
15
  formatUsd,
16
- } from "../helpers/index.js";
16
+ } from "../../presentation/format.js";
17
17
  import { resolveBackendForChat } from "../model-menu.js";
18
18
  import { getBackendIdForChat } from "../../../core/engine/backend-controller/index.js";
19
19
  import {
20
20
  performSessionReset,
21
21
  collectSessionStatus,
22
- } from "../../shared/session-status.js";
22
+ } from "../../presentation/session-status.js";
23
23
  import {
24
24
  formatCacheTempLine,
25
25
  formatDaemonLine,
26
- } from "../../shared/status-context.js";
26
+ } from "../../presentation/status-context.js";
27
27
  import { stopCurrentTurn } from "../../../core/engine/dispatcher.js";
28
28
  import type { RegisterDeps } from "./state.js";
29
29
 
@@ -23,13 +23,15 @@ import {
23
23
  formatModelLabel,
24
24
  formatDuration,
25
25
  parseInterval,
26
+ } from "../../presentation/format.js";
27
+ import {
26
28
  renderSettingsText,
27
29
  renderSettingsKeyboard,
28
30
  renderEffortRows,
29
31
  renderModelMenuText,
30
32
  renderModelMenuKeyboard,
31
33
  type SettingsButton,
32
- } from "../helpers/index.js";
34
+ } from "../render/menu.js";
33
35
  import {
34
36
  buildModelMenuViewForChat,
35
37
  resolveBackendForChat,
@@ -40,7 +42,7 @@ import {
40
42
  displayReasoningEffort,
41
43
  getActiveReasoningLevels,
42
44
  supportsReasoningLevel,
43
- } from "../../shared/reasoning-levels.js";
45
+ } from "../../presentation/reasoning-levels.js";
44
46
  import type { RegisterDeps } from "./state.js";
45
47
 
46
48
  /** `/model <query>` — resolve the query against the per-chat backend and
@@ -13,7 +13,7 @@ import {
13
13
  createDmUserTracker,
14
14
  createNoticeCooldown,
15
15
  createRateLimiter,
16
- } from "../../shared/access.js";
16
+ } from "../../presentation/access.js";
17
17
 
18
18
  // ── First-time DM user tracking ──────────────────────────────────────────────
19
19
 
@@ -32,7 +32,7 @@ import {
32
32
  buildModelMenuState,
33
33
  type ModelMenuState,
34
34
  type SettingsButton,
35
- } from "./helpers/index.js";
35
+ } from "./render/menu.js";
36
36
  import {
37
37
  hasBackendPool,
38
38
  getBackendIdForChat,
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Telegram's dialect of the shared chat reports.
3
+ *
4
+ * `frontend/presentation/reports.ts` writes each report once and takes
5
+ * the markup spelling from a `ReportFormatter`; this is Telegram's.
6
+ * `escape` is `escapeHtml` because sends use `parse_mode: HTML`, so a
7
+ * literal `<` in a device name, a model id, a metric key or a backend
8
+ * note would be read as a tag and 400 the entire message.
9
+ *
10
+ * Its own module so both `reports.ts` (the report wrappers) and
11
+ * `menu.ts` (the settings body) can read it without a cycle.
12
+ */
13
+
14
+ import { escapeHtml } from "../formatting.js";
15
+ import type { ReportFormatter } from "../../presentation/reports.js";
16
+
17
+ /** Telegram hard-caps a message at 4096 characters; this leaves headroom. */
18
+ export const TELEGRAM_MESSAGE_MAX = 3800;
19
+
20
+ export const TELEGRAM_REPORTS: ReportFormatter = {
21
+ bold: (s) => `<b>${s}</b>`,
22
+ italic: (s) => `<i>${s}</i>`,
23
+ // Telegram has one italic tag; Discord spells this one `*…*`.
24
+ emphasis: (s) => `<i>${s}</i>`,
25
+ code: (s) => `<code>${s}</code>`,
26
+ escape: escapeHtml,
27
+ lineLimit: TELEGRAM_MESSAGE_MAX,
28
+ metricLabelMax: 80,
29
+ pulseLabel: "Pulse:",
30
+ };
@@ -1,19 +1,49 @@
1
1
  /**
2
2
  * Settings + model-picker UI: settings text, the `/model` main/browse/backend
3
- * keyboards, effort rows, and the menu-state builder. All pure rendering.
3
+ * keyboards, effort rows, the model-option list, and the menu-state builder.
4
+ * All pure rendering.
5
+ *
6
+ * The settings body itself is `frontend/presentation/reports.ts`, rendered
7
+ * with Telegram's dialect; everything below it is inline-keyboard shape,
8
+ * which has no counterpart on Discord (Components, not keyboards).
4
9
  */
5
10
 
6
11
  import { escapeHtml } from "../formatting.js";
7
12
  import type { ReasoningEffortLevel } from "../../../core/types.js";
8
13
  import { REASONING_LEVEL_LABELS } from "../../../core/models/reasoning-levels.js";
9
- import { resolveModel, resolveModelId } from "../../../core/models/catalog.js";
10
14
  import {
11
- DEFAULT_PULSE_INTERVAL_MS,
12
- formatDuration,
13
- formatModelLabel,
14
- formatCompactModelLabel,
15
- getTelegramModelOptions,
16
- } from "./format.js";
15
+ getModels,
16
+ resolveModel,
17
+ resolveModelId,
18
+ type ModelInfo,
19
+ } from "../../../core/models/catalog.js";
20
+ import { renderSettingsText as renderSettingsTextWith } from "../../presentation/reports.js";
21
+ import { TELEGRAM_REPORTS } from "./html.js";
22
+
23
+ /** Display name for a known ModelInfo. */
24
+ export function formatModelOptionLabel(model: ModelInfo): string {
25
+ return model.displayName;
26
+ }
27
+
28
+ /** Compact display name for a known ModelInfo. */
29
+ export function formatCompactModelLabel(model: ModelInfo): string {
30
+ return model.displayName;
31
+ }
32
+
33
+ /** One option per distinct display name, in catalog order. */
34
+ export function getTelegramModelOptions(): ModelInfo[] {
35
+ const options: ModelInfo[] = [];
36
+ const seenKeys = new Set<string>();
37
+
38
+ for (const model of getModels()) {
39
+ const key = model.displayName.toLowerCase();
40
+ if (seenKeys.has(key)) continue;
41
+ seenKeys.add(key);
42
+ options.push(model);
43
+ }
44
+
45
+ return options;
46
+ }
17
47
 
18
48
  export function renderSettingsText(
19
49
  model: string,
@@ -22,17 +52,14 @@ export function renderSettingsText(
22
52
  pulseIntervalMs?: number,
23
53
  modelDetails?: Array<string>,
24
54
  ): string {
25
- const intervalStr = pulseIntervalMs
26
- ? formatDuration(pulseIntervalMs)
27
- : formatDuration(DEFAULT_PULSE_INTERVAL_MS);
28
- return [
29
- "<b>🦅 Settings</b>",
30
- "",
31
- `<b>Model:</b> <code>${escapeHtml(formatModelLabel(model))}</code>`,
32
- ...(modelDetails?.length ? modelDetails.map(escapeHtml) : []),
33
- `<b>Effort:</b> ${effort}`,
34
- `<b>Pulse:</b> ${proactive ? "on" : "off"} (every ${intervalStr})`,
35
- ].join("\n");
55
+ return renderSettingsTextWith(
56
+ TELEGRAM_REPORTS,
57
+ model,
58
+ effort,
59
+ proactive,
60
+ pulseIntervalMs,
61
+ modelDetails,
62
+ );
36
63
  }
37
64
 
38
65
  export function isSelectedModel(
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Metrics, doctor, mesh and usage reports for Telegram (HTML messages).
3
+ *
4
+ * The reports themselves live in `frontend/presentation/reports.ts` and
5
+ * the HTML dialect in `./html.ts`; this file is the wrappers that keep
6
+ * each command to a single import, plus the two Telegram-only pieces:
7
+ * the /metrics grain keyboard and the `/mesh` bridge footer.
8
+ */
9
+
10
+ import { escapeHtml } from "../formatting.js";
11
+ import type { DoctorReport } from "../../../core/doctor/index.js";
12
+ import type { MeshPingResult } from "../../../core/mesh/service.js";
13
+ import type { BackendUsageEntry } from "../../presentation/plan-usage-report.js";
14
+ import type { SettingsButton } from "./menu.js";
15
+ import {
16
+ renderDoctorReport,
17
+ renderMeshReport as renderMeshReportWith,
18
+ renderMetricsPanel as renderMetricsPanelWith,
19
+ renderUsageMessage as renderUsageMessageWith,
20
+ type MetricsSnapshot,
21
+ } from "../../presentation/reports.js";
22
+ import { TELEGRAM_MESSAGE_MAX, TELEGRAM_REPORTS } from "./html.js";
23
+
24
+ /**
25
+ * Rows shown in the `tool_calls` leaderboard before the tail collapses
26
+ * into a "…and N more" line. A long-lived bot accumulates a lifetime
27
+ * tool-call entry per distinct tool name (easily 100+ once plugin and
28
+ * MCP tools are counted), which is what used to push /metrics past
29
+ * Telegram's message limit and split it across messages.
30
+ */
31
+ const TOOL_CALLS_TOP_N = 12;
32
+
33
+ /** Which grain the /metrics panel is currently showing. */
34
+ export type MetricsView = "today" | "all";
35
+
36
+ const VIEW_TITLES: Record<MetricsView, string> = {
37
+ today: "Metrics — today (UTC)",
38
+ all: "Metrics — all time",
39
+ };
40
+
41
+ /**
42
+ * Render the /metrics panel for one grain as a SINGLE Telegram message.
43
+ * Pair it with `renderMetricsKeyboard(view)` — the Today / All time
44
+ * buttons swap grains by editing this message in place.
45
+ */
46
+ export function renderMetricsPanel(
47
+ metrics: MetricsSnapshot,
48
+ view: MetricsView,
49
+ maxLen = TELEGRAM_MESSAGE_MAX,
50
+ ): string {
51
+ return renderMetricsPanelWith(
52
+ metrics,
53
+ TELEGRAM_REPORTS,
54
+ VIEW_TITLES[view],
55
+ maxLen,
56
+ TOOL_CALLS_TOP_N,
57
+ );
58
+ }
59
+
60
+ /** Grain-switch buttons for the /metrics panel. */
61
+ export function renderMetricsKeyboard(
62
+ view: MetricsView,
63
+ ): Array<Array<SettingsButton>> {
64
+ return [
65
+ [
66
+ {
67
+ text: view === "today" ? "✓ Today" : "Today",
68
+ callback_data: "metrics:today",
69
+ },
70
+ {
71
+ text: view === "all" ? "✓ All time" : "All time",
72
+ callback_data: "metrics:all",
73
+ },
74
+ ],
75
+ ];
76
+ }
77
+
78
+ /** Render the `/usage` report — one block per exposed backend. */
79
+ export function renderUsageMessage(entries: BackendUsageEntry[]): string {
80
+ return renderUsageMessageWith(entries, TELEGRAM_REPORTS);
81
+ }
82
+
83
+ /**
84
+ * Render a DoctorReport as one Telegram HTML message. Same data as
85
+ * `talon doctor` (src/core/doctor/) plus in-process runtime info — when
86
+ * this renders, the bot is by definition running, so the CLI's "is the
87
+ * bot up" probe becomes an uptime line instead.
88
+ */
89
+ export function renderDoctorMessage(report: DoctorReport): string {
90
+ return renderDoctorReport(report, TELEGRAM_REPORTS);
91
+ }
92
+
93
+ // ── /mesh ───────────────────────────────────────────────────────────────────
94
+
95
+ /**
96
+ * Render the `/mesh` fleet report as one HTML message, with the bridge
97
+ * footer appended when the caller supplies reachability.
98
+ */
99
+ export function renderMeshReport(
100
+ results: MeshPingResult[],
101
+ now = Date.now(),
102
+ bridge?: MeshReachability,
103
+ ): string {
104
+ // An empty fleet is the one case where "what do I point a device at?" is
105
+ // the only useful thing to say, so the bridge line carries the answer
106
+ // instead of leaving the operator to hunt for host and port.
107
+ return renderMeshReportWith(
108
+ results,
109
+ TELEGRAM_REPORTS,
110
+ now,
111
+ bridgeLines(bridge),
112
+ );
113
+ }
114
+
115
+ /** What `/mesh` says about the bridge a new device would dial. */
116
+ export type MeshReachability =
117
+ | {
118
+ ok: true;
119
+ url: string;
120
+ authRequired: boolean;
121
+ token?: string;
122
+ fingerprint?: string;
123
+ }
124
+ | { ok: false; text: string };
125
+
126
+ /**
127
+ * The bridge footer.
128
+ *
129
+ * The bearer token and certificate appear when the caller passed them, which
130
+ * `/mesh` does for the configured admin and not for anyone else: an operator
131
+ * asking their own daemon how to reach itself should get the answer, while a
132
+ * group member reading the fleet has no business holding the key to it.
133
+ */
134
+ function bridgeLines(bridge?: MeshReachability): string[] {
135
+ if (!bridge) return [];
136
+ if (!bridge.ok) return ["", `<b>Bridge</b> — ${escapeHtml(bridge.text)}`];
137
+ const auth = bridge.authRequired ? "token required" : "no token";
138
+ return [
139
+ "",
140
+ `<b>Bridge</b> <code>${escapeHtml(bridge.url)}</code> · ${auth}`,
141
+ ...(bridge.token ? [`Token <code>${escapeHtml(bridge.token)}</code>`] : []),
142
+ ...(bridge.fingerprint
143
+ ? [`Certificate <code>${escapeHtml(bridge.fingerprint)}</code>`]
144
+ : []),
145
+ "Run <code>/mesh link</code> for a one-tap pairing link.",
146
+ ];
147
+ }
148
+
149
+ /**
150
+ * Render a minted pairing link, or why one couldn't be minted.
151
+ *
152
+ * The values are printed alongside the link because the link's whole payoff
153
+ * — the phone configuring itself — depends on the companion already being
154
+ * installed, and the fallback needs to be right there when it isn't.
155
+ */
156
+ export function renderMeshPairLink(
157
+ minted:
158
+ | {
159
+ ok: true;
160
+ link: string;
161
+ url: string;
162
+ token: string;
163
+ fingerprint?: string;
164
+ }
165
+ | { ok: false; text: string },
166
+ ): string {
167
+ if (!minted.ok) {
168
+ return `<b>Pairing</b>\n\n${escapeHtml(minted.text)}`;
169
+ }
170
+ return [
171
+ "<b>Pair a device</b>",
172
+ "",
173
+ `Open on the phone: ${escapeHtml(minted.link)}`,
174
+ "",
175
+ "Single-use, expires in 10 minutes. If the companion isn't installed yet:",
176
+ ` Bridge <code>${escapeHtml(minted.url)}</code>`,
177
+ ` Token <code>${escapeHtml(minted.token)}</code>`,
178
+ ...(minted.fingerprint
179
+ ? [` Certificate <code>${escapeHtml(minted.fingerprint)}</code>`]
180
+ : []),
181
+ ].join("\n");
182
+ }
@@ -7,9 +7,9 @@ import {
7
7
  apportionCells,
8
8
  type ContextBreakdown,
9
9
  type ContextSegmentKey,
10
- } from "../../shared/status-context.js";
10
+ } from "../../presentation/status-context.js";
11
11
  import { getRecentHistory } from "../../../storage/history.js";
12
- import { formatTokenCount } from "../../shared/format.js";
12
+ import { formatTokenCount } from "../../presentation/format.js";
13
13
  import { getChatSettings } from "../../../storage/chat-settings.js";
14
14
  import { resolveModelId as resolveModelName } from "../../../core/models/catalog.js";
15
15
  import { getSessionInfo } from "../../../storage/sessions.js";
@@ -2,7 +2,7 @@
2
2
 
3
3
  import pc from "picocolors";
4
4
  import { formatTimeAgo } from "../renderer.js";
5
- import { isTerminalChatId } from "../../../util/chat-id.js";
5
+ import { isTerminalChatId } from "../../../core/frontend-runtime/chat-id.js";
6
6
  import { resolveModel as coreResolveModel } from "../../../core/models/catalog.js";
7
7
  import {
8
8
  getAllSessions,
@@ -5,12 +5,12 @@ import {
5
5
  buildCacheDisplay,
6
6
  buildContextDisplay,
7
7
  buildPlanDisplay,
8
- } from "../../shared/status-context.js";
8
+ } from "../../presentation/status-context.js";
9
9
  import {
10
10
  formatDuration,
11
11
  formatTokenCount,
12
12
  formatUsd,
13
- } from "../../shared/format.js";
13
+ } from "../../presentation/format.js";
14
14
  import { getChatSettings } from "../../../storage/chat-settings.js";
15
15
  import { getSessionInfo } from "../../../storage/sessions.js";
16
16
  import { getLoadedPlugins } from "../../../core/plugin/index.js";
@@ -16,7 +16,7 @@ import { log } from "../../util/log.js";
16
16
  import {
17
17
  deriveNumericChatId,
18
18
  generateTerminalChatId,
19
- } from "../../util/chat-id.js";
19
+ } from "../../core/frontend-runtime/chat-id.js";
20
20
  import { resolveModel } from "../../core/models/catalog.js";
21
21
  import { toolInputToRecord } from "../../core/agent-runtime/events.js";
22
22
  import { createRenderer } from "./renderer.js";
@@ -27,7 +27,7 @@ import {
27
27
  clearCommands,
28
28
  type CommandContext,
29
29
  } from "./commands.js";
30
- import { buildCacheDisplay } from "../shared/status-context.js";
30
+ import { buildCacheDisplay } from "../presentation/status-context.js";
31
31
 
32
32
  // ── State ────────────────────────────────────────────────────────────────────
33
33
 
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import pc from "picocolors";
10
- import { formatUsd } from "../shared/format.js";
10
+ import { formatUsd } from "../presentation/format.js";
11
11
 
12
12
  // ── Types ────────────────────────────────────────────────────────────────────
13
13
 
@@ -5,7 +5,7 @@
5
5
  * WhatsApp has no inline buttons, so everything Telegram does with a
6
6
  * picker is done here with typed arguments: `/model` prints a numbered
7
7
  * catalog, `/model 3` (or `/model <id>`, `/model <backend>`) picks from
8
- * it. The state changes come from frontend/shared/model-commands.ts;
8
+ * it. The state changes come from frontend/presentation/model-commands.ts;
9
9
  * this module only parses, authorises, and renders.
10
10
  *
11
11
  * Replies are written in Markdown — `sendText` translates them into
@@ -23,16 +23,16 @@ import {
23
23
  formatModelLabel,
24
24
  formatTokenCount,
25
25
  formatUsd,
26
- } from "../shared/format.js";
26
+ } from "../presentation/format.js";
27
27
  import {
28
28
  collectSessionStatus,
29
29
  performSessionReset,
30
30
  type SessionStatusData,
31
- } from "../shared/session-status.js";
31
+ } from "../presentation/session-status.js";
32
32
  import {
33
33
  formatCacheTempLine,
34
34
  formatDaemonLine,
35
- } from "../shared/status-context.js";
35
+ } from "../presentation/status-context.js";
36
36
  import {
37
37
  describeChatEffort,
38
38
  describeChatModels,
@@ -45,7 +45,7 @@ import {
45
45
  switchChatBackend,
46
46
  type ModelCommandDeps,
47
47
  type ModelOverview,
48
- } from "../shared/model-commands.js";
48
+ } from "../presentation/model-commands.js";
49
49
  import { sendText } from "./actions/send.js";
50
50
  import { identityAllowed, type Identity } from "./connection/identity.js";
51
51
  import type { WhatsAppChatInfo } from "./registry.js";
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { isJidGroup, jidNormalizedUser } from "baileys";
12
- import { deriveNumericChatId } from "../../util/chat-id.js";
12
+ import { deriveNumericChatId } from "../../core/frontend-runtime/chat-id.js";
13
13
  import { bareId } from "./connection/identity.js";
14
14
 
15
15
  export type WhatsAppChatInfo = {
package/src/index.ts CHANGED
@@ -3,12 +3,15 @@
3
3
  *
4
4
  * Dispatches the hidden `_mcp-launch` (MCP supervisor) and `_lua-run`
5
5
  * (WASM Lua trigger runner) subcommands BEFORE the app graph loads —
6
- * both are Talon re-invoking itself (see util/mcp-launcher.ts). The
6
+ * both are Talon re-invoking itself (see core/mcp-hub/launcher.ts). The
7
7
  * dynamic import keeps these helper processes light: they evaluate this
8
8
  * shim and their own module, never the backends/frontends/plugins.
9
9
  */
10
10
 
11
- import { MCP_LAUNCH_SUBCOMMAND, runSupervisor } from "./util/mcp-launcher.js";
11
+ import {
12
+ MCP_LAUNCH_SUBCOMMAND,
13
+ runSupervisor,
14
+ } from "./core/mcp-hub/launcher.js";
12
15
  import { LUA_RUN_SUBCOMMAND, runLuaMain } from "./core/scripts/lua.js";
13
16
 
14
17
  if (process.argv[2] === MCP_LAUNCH_SUBCOMMAND) {
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Bun-COMPILED binaries are a third shape detected separately (argv[1]
13
13
  * points into the embedded virtual fs) — see the existing `$bunfs`
14
- * checks at the spawn sites and in `util/mcp-launcher.ts`.
14
+ * checks at the spawn sites and in `core/mcp-hub/launcher.ts`.
15
15
  */
16
16
 
17
17
  /** True when the current process is executing under the Bun runtime. */