talon-agent 4.3.2 → 4.4.0

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 (146) hide show
  1. package/package.json +1 -1
  2. package/src/app.ts +1 -1
  3. package/src/backend/claude-sdk/doctor.ts +1 -1
  4. package/src/backend/claude-sdk/state.ts +1 -1
  5. package/src/backend/codex/doctor.ts +1 -1
  6. package/src/backend/codex/init.ts +1 -1
  7. package/src/backend/codex/state.ts +1 -1
  8. package/src/backend/openai-agents/doctor.ts +1 -1
  9. package/src/backend/openai-agents/init.ts +1 -1
  10. package/src/backend/openai-agents/state.ts +1 -1
  11. package/src/backend/remote-server/factory.ts +1 -1
  12. package/src/backend/remote-server/mcp.ts +1 -1
  13. package/src/backend/remote-server/server-bindings.ts +1 -1
  14. package/src/backend/remote-server/state.ts +1 -1
  15. package/src/backend/shared/system-prompt.ts +1 -1
  16. package/src/bootstrap.ts +3 -3
  17. package/src/cli/chat.ts +1 -1
  18. package/src/cli/config.ts +2 -2
  19. package/src/cli/doctor.ts +1 -1
  20. package/src/cli/index.ts +8 -0
  21. package/src/cli/memory.ts +166 -0
  22. package/src/cli/setup.ts +1 -1
  23. package/src/core/agent-runtime/backend-registry.ts +2 -2
  24. package/src/core/agent-runtime/model-ref.ts +2 -2
  25. package/src/core/auth/expiry-monitor.ts +1 -1
  26. package/src/{util/config.ts → core/config/index.ts} +8 -8
  27. package/src/core/{doctor.ts → doctor/index.ts} +11 -11
  28. package/src/core/engine/backend-controller/legacy.ts +1 -1
  29. package/src/core/engine/backend-controller/pool.ts +1 -1
  30. package/src/core/engine/backend-controller/rebind.ts +1 -1
  31. package/src/core/engine/backend-controller/state.ts +1 -1
  32. package/src/core/engine/gateway-actions/plugins.ts +1 -1
  33. package/src/core/engine/model-audit.ts +1 -1
  34. package/src/core/{notify.ts → frontend-runtime/admin-notify.ts} +1 -1
  35. package/src/core/frontend-runtime/capabilities.ts +1 -1
  36. package/src/core/models/active-model.ts +1 -1
  37. package/src/core/plugin/builtins.ts +2 -2
  38. package/src/core/plugin/manage.ts +1 -1
  39. package/src/core/plugin/native-runtimes.ts +1 -1
  40. package/src/frontend/discord/admin.ts +1 -1
  41. package/src/frontend/discord/callbacks/components/index.ts +1 -1
  42. package/src/frontend/discord/callbacks/components/settings.ts +1 -1
  43. package/src/frontend/discord/callbacks/components/types.ts +1 -1
  44. package/src/frontend/discord/callbacks/modals.ts +1 -1
  45. package/src/frontend/discord/commands/admin.ts +2 -2
  46. package/src/frontend/discord/commands/definitions.ts +1 -1
  47. package/src/frontend/discord/commands/info.ts +1 -1
  48. package/src/frontend/discord/commands/router.ts +1 -1
  49. package/src/frontend/discord/commands/session.ts +1 -1
  50. package/src/frontend/discord/commands/settings.ts +1 -1
  51. package/src/frontend/discord/handlers/delivery.ts +1 -1
  52. package/src/frontend/discord/handlers/messages.ts +1 -1
  53. package/src/frontend/discord/handlers/queue.ts +1 -1
  54. package/src/frontend/discord/handlers/state.ts +1 -1
  55. package/src/frontend/discord/index.ts +1 -1
  56. package/src/frontend/discord/middleware.ts +1 -1
  57. package/src/frontend/discord/render.ts +1 -1
  58. package/src/frontend/discord/runtime.ts +1 -1
  59. package/src/frontend/native/extensions.ts +4 -1
  60. package/src/frontend/native/index.ts +1 -1
  61. package/src/frontend/native/runtime.ts +1 -1
  62. package/src/frontend/native/settings.ts +1 -1
  63. package/src/frontend/shared/model-commands.ts +1 -1
  64. package/src/frontend/shared/plan-usage-report.ts +1 -1
  65. package/src/frontend/shared/reasoning-levels.ts +1 -1
  66. package/src/frontend/shared/session-status.ts +1 -1
  67. package/src/frontend/teams/index.ts +1 -1
  68. package/src/frontend/teams/runtime.ts +1 -1
  69. package/src/frontend/telegram/actions/chat-info.ts +1 -1
  70. package/src/frontend/telegram/actions/coerce.ts +15 -0
  71. package/src/frontend/telegram/actions/index.ts +2 -2
  72. package/src/frontend/telegram/actions/media.ts +1 -1
  73. package/src/frontend/telegram/actions/messaging.ts +3 -4
  74. package/src/frontend/telegram/actions/moderation/chat.ts +63 -0
  75. package/src/frontend/telegram/actions/moderation/index.ts +74 -0
  76. package/src/frontend/telegram/actions/moderation/members.ts +77 -0
  77. package/src/frontend/telegram/actions/moderation/permissions.ts +69 -0
  78. package/src/frontend/telegram/actions/moderation/topics.ts +48 -0
  79. package/src/frontend/telegram/actions/moderation/types.ts +16 -0
  80. package/src/frontend/telegram/actions/rich-messages.ts +51 -0
  81. package/src/frontend/telegram/actions/{shared.ts → send.ts} +9 -59
  82. package/src/frontend/telegram/admin/background.ts +66 -0
  83. package/src/frontend/telegram/admin/chunked-reply.ts +20 -0
  84. package/src/frontend/telegram/admin/health.ts +81 -0
  85. package/src/frontend/telegram/admin/sessions.ts +105 -0
  86. package/src/frontend/telegram/admin.ts +57 -262
  87. package/src/frontend/telegram/callbacks/auth.ts +1 -1
  88. package/src/frontend/telegram/callbacks/effort.ts +1 -1
  89. package/src/frontend/telegram/callbacks/index.ts +3 -3
  90. package/src/frontend/telegram/callbacks/metrics.ts +1 -1
  91. package/src/frontend/telegram/callbacks/model/backend.ts +156 -0
  92. package/src/frontend/telegram/callbacks/model/control.ts +29 -0
  93. package/src/frontend/telegram/callbacks/model/nav.ts +54 -0
  94. package/src/frontend/telegram/callbacks/model/select.ts +100 -0
  95. package/src/frontend/telegram/callbacks/model/types.ts +39 -0
  96. package/src/frontend/telegram/callbacks/model/views.ts +116 -0
  97. package/src/frontend/telegram/callbacks/model.ts +61 -331
  98. package/src/frontend/telegram/callbacks/pulse.ts +1 -1
  99. package/src/frontend/telegram/callbacks/{shared.ts → query.ts} +3 -2
  100. package/src/frontend/telegram/callbacks/settings.ts +1 -1
  101. package/src/frontend/telegram/callbacks/whatsapp.ts +1 -1
  102. package/src/frontend/telegram/commands/admin.ts +108 -77
  103. package/src/frontend/telegram/commands/index.ts +1 -1
  104. package/src/frontend/telegram/commands/settings.ts +65 -43
  105. package/src/frontend/telegram/commands/state.ts +1 -1
  106. package/src/frontend/telegram/commands/whatsapp-pairing.ts +1 -1
  107. package/src/frontend/telegram/handlers/context.ts +1 -1
  108. package/src/frontend/telegram/handlers/delivery.ts +2 -2
  109. package/src/frontend/telegram/handlers/messages.ts +1 -1
  110. package/src/frontend/telegram/handlers/queue.ts +1 -1
  111. package/src/frontend/telegram/handlers/state.ts +1 -1
  112. package/src/frontend/telegram/helpers/diagnostics.ts +1 -1
  113. package/src/frontend/telegram/index.ts +1 -1
  114. package/src/frontend/telegram/middleware.ts +167 -202
  115. package/src/frontend/telegram/model-menu.ts +1 -1
  116. package/src/frontend/terminal/builtins/context.ts +184 -0
  117. package/src/frontend/terminal/builtins/help.ts +35 -0
  118. package/src/frontend/terminal/builtins/index.ts +23 -0
  119. package/src/frontend/terminal/builtins/model.ts +198 -0
  120. package/src/frontend/terminal/builtins/session.ts +101 -0
  121. package/src/frontend/terminal/builtins/status.ts +202 -0
  122. package/src/frontend/terminal/command-registry.ts +80 -0
  123. package/src/frontend/terminal/commands.ts +11 -674
  124. package/src/frontend/terminal/index.ts +1 -1
  125. package/src/frontend/terminal/input/history.ts +64 -0
  126. package/src/frontend/terminal/input/keys.ts +143 -0
  127. package/src/frontend/terminal/input/parts.ts +33 -0
  128. package/src/frontend/terminal/input/paste.ts +53 -0
  129. package/src/frontend/terminal/input/render.ts +42 -0
  130. package/src/frontend/terminal/input/state.ts +60 -0
  131. package/src/frontend/terminal/input.ts +27 -342
  132. package/src/frontend/whatsapp/connection.ts +1 -1
  133. package/src/frontend/whatsapp/index.ts +2 -2
  134. package/src/frontend/whatsapp/pairing-service.ts +1 -1
  135. package/src/frontend/whatsapp/runtime.ts +1 -1
  136. package/src/plugins/github/provision.ts +1 -1
  137. package/src/plugins/mempalace/provision.ts +1 -1
  138. package/src/plugins/playwright/provision.ts +1 -1
  139. package/src/storage/memory.ts +505 -0
  140. package/src/storage/repositories/memory-repo.ts +308 -0
  141. package/src/storage/sql/memory.sql +95 -0
  142. package/src/storage/sql/schema.sql +81 -0
  143. package/src/storage/sql/statements.generated.ts +146 -0
  144. package/src/frontend/telegram/actions/moderation.ts +0 -255
  145. /package/src/core/{doctor-types.ts → doctor/types.ts} +0 -0
  146. /package/src/core/{pairing-broker.ts → frontend-runtime/pairing-broker.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talon-agent",
3
- "version": "4.3.2",
3
+ "version": "4.4.0",
4
4
  "description": "Multi-frontend AI agent with full tool access, streaming, cron jobs, and plugin system",
5
5
  "author": "Dylan Neve",
6
6
  "license": "MIT",
package/src/app.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * are loaded dynamically — only the selected platform's dependencies are required.
7
7
  */
8
8
 
9
- import { getFrontends } from "./util/config.js";
9
+ import { getFrontends } from "./core/config/index.js";
10
10
  import { startUploadCleanup, stopUploadCleanup } from "./util/workspace.js";
11
11
  import { flushDatabase } from "./storage/db.js";
12
12
  import { getActiveCount, stopAllTurns } from "./core/engine/dispatcher.js";
@@ -7,7 +7,7 @@
7
7
  import type {
8
8
  DoctorCheck,
9
9
  DoctorConfigSlice,
10
- } from "../../core/doctor-types.js";
10
+ } from "../../core/doctor/types.js";
11
11
  import { getModels } from "../../core/models/catalog.js";
12
12
  import { binaryOnPath } from "../../util/binary-on-path.js";
13
13
  import { resolveModel } from "./model-provider.js";
@@ -5,7 +5,7 @@
5
5
  * initialization functions + internal getters for sibling modules.
6
6
  */
7
7
 
8
- import type { TalonConfig } from "../../util/config.js";
8
+ import type { TalonConfig } from "../../core/config/index.js";
9
9
  import { registerClaudeModels } from "./models/index.js";
10
10
 
11
11
  // ── State ────────────────────────────────────────────────────────────────────
@@ -6,7 +6,7 @@
6
6
  import type {
7
7
  DoctorCheck,
8
8
  DoctorConfigSlice,
9
- } from "../../core/doctor-types.js";
9
+ } from "../../core/doctor/types.js";
10
10
  import { binaryOnPath } from "../../util/binary-on-path.js";
11
11
  import { detectCodexAuth } from "./auth.js";
12
12
 
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { Codex } from "@openai/codex-sdk";
25
- import type { TalonConfig } from "../../util/config.js";
25
+ import type { TalonConfig } from "../../core/config/index.js";
26
26
  import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
27
27
  import { log, logWarn } from "../../util/log.js";
28
28
  import { nonTerminalFrontends } from "../shared/frontends.js";
@@ -11,7 +11,7 @@
11
11
 
12
12
  import type { Codex } from "@openai/codex-sdk";
13
13
  import type { ReasoningEffortLevel } from "../../core/types.js";
14
- import type { TalonConfig } from "../../util/config.js";
14
+ import type { TalonConfig } from "../../core/config/index.js";
15
15
  import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
16
16
 
17
17
  /**
@@ -6,7 +6,7 @@
6
6
  import type {
7
7
  DoctorCheck,
8
8
  DoctorConfigSlice,
9
- } from "../../core/doctor-types.js";
9
+ } from "../../core/doctor/types.js";
10
10
 
11
11
  export async function openAIAgentsDoctorChecks(
12
12
  config: DoctorConfigSlice | undefined,
@@ -43,7 +43,7 @@
43
43
 
44
44
  import OpenAI from "openai";
45
45
  import { setDefaultOpenAIClient, setOpenAIAPI } from "@openai/agents";
46
- import type { TalonConfig } from "../../util/config.js";
46
+ import type { TalonConfig } from "../../core/config/index.js";
47
47
  import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
48
48
  import { log, logWarn } from "../../util/log.js";
49
49
  import { nonTerminalFrontends } from "../shared/frontends.js";
@@ -8,7 +8,7 @@
8
8
  * so the resolver, picker, and UIs can read it synchronously.
9
9
  */
10
10
 
11
- import type { TalonConfig } from "../../util/config.js";
11
+ import type { TalonConfig } from "../../core/config/index.js";
12
12
  import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
13
13
  import { TalonSession } from "./session.js";
14
14
  import type { ReasoningEffortLevel } from "../../core/types.js";
@@ -25,7 +25,7 @@ import {
25
25
  type UsageTelemetry,
26
26
  } from "../../core/agent-runtime/capabilities.js";
27
27
  import type { OneShotAgentParams, OneShotUsage } from "../../core/types.js";
28
- import type { TalonConfig } from "../../util/config.js";
28
+ import type { TalonConfig } from "../../core/config/index.js";
29
29
  import { binaryOnPath } from "../../util/binary-on-path.js";
30
30
  import { log } from "../../util/log.js";
31
31
  import { handlerToEvents } from "../shared/handler-to-events.js";
@@ -44,7 +44,7 @@ import {
44
44
  import type { RemoteAgentClient } from "./client.js";
45
45
  import type { RemoteServerState } from "./state.js";
46
46
  import { errMsg } from "./state.js";
47
- import type { TalonConfig } from "../../util/config.js";
47
+ import type { TalonConfig } from "../../core/config/index.js";
48
48
  import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
49
49
  import { ALL_TOOLS, nativeTools } from "../../core/tools/index.js";
50
50
  import {
@@ -20,7 +20,7 @@
20
20
  * `vi.mock("../backend/<name>/server.js")` all address.
21
21
  */
22
22
 
23
- import type { TalonConfig } from "../../util/config.js";
23
+ import type { TalonConfig } from "../../core/config/index.js";
24
24
  import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
25
25
  import {
26
26
  buildDeliveryContract,
@@ -15,7 +15,7 @@
15
15
  * fields) and avoids the "where does this method live" guessing game.
16
16
  */
17
17
 
18
- import type { TalonConfig } from "../../util/config.js";
18
+ import type { TalonConfig } from "../../core/config/index.js";
19
19
  import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
20
20
  import type { RemoteAgentClient } from "./client.js";
21
21
 
@@ -39,7 +39,7 @@ import {
39
39
  joinSystemPromptParts,
40
40
  type SystemPromptParts,
41
41
  type TalonConfig,
42
- } from "../../util/config.js";
42
+ } from "../../core/config/index.js";
43
43
  import { getPluginPromptAdditions } from "../../core/plugin/index.js";
44
44
  import { onPromptInputsChanged } from "../../core/prompt/invalidation.js";
45
45
  import { frontendForChatId, nonTerminalFrontends } from "./frontends.js";
package/src/bootstrap.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * differ (index.ts selects dynamically, cli.ts always uses terminal).
10
10
  */
11
11
 
12
- import { loadConfig, rebuildSystemPrompt } from "./util/config.js";
12
+ import { loadConfig, rebuildSystemPrompt } from "./core/config/index.js";
13
13
  import { initWorkspace } from "./util/workspace.js";
14
14
  import { loadSessions, resetSession } from "./storage/sessions.js";
15
15
  import { loadChatSettings } from "./storage/chat-settings.js";
@@ -27,7 +27,7 @@ import { appendToJournal } from "./storage/journal.js";
27
27
  import { initPulse, resetPulseTimer } from "./core/background/pulse.js";
28
28
  import { initCron } from "./core/background/cron.js";
29
29
  import { initPlanAlerts } from "./core/background/plan-alerts.js";
30
- import { setAdminNotifier } from "./core/notify.js";
30
+ import { setAdminNotifier } from "./core/frontend-runtime/admin-notify.js";
31
31
  import { startAuthExpiryMonitor } from "./core/auth/expiry-monitor.js";
32
32
  import {
33
33
  initTriggers,
@@ -38,7 +38,7 @@ import { initHeartbeat } from "./core/background/heartbeat/index.js";
38
38
  import { log, logWarn, logDebug } from "./util/log.js";
39
39
  import { bootPhase } from "./util/boot-timer.js";
40
40
  import { mapConcurrent } from "./util/concurrency.js";
41
- import type { TalonConfig } from "./util/config.js";
41
+ import type { TalonConfig } from "./core/config/index.js";
42
42
  import { resolveFrontendIdAmong } from "./core/frontend-runtime/routing.js";
43
43
  import type { Frontend } from "./core/frontend-runtime/index.js";
44
44
  import type { ContextManager } from "./core/types.js";
package/src/cli/chat.ts CHANGED
@@ -21,7 +21,7 @@ export async function startChat(): Promise<void> {
21
21
  // spawning telegram-tools or teams-tools MCP servers and ensures the
22
22
  // system prompt loads terminal.md instead of teams.md/telegram.md.
23
23
  (config as Record<string, unknown>).frontend = "terminal";
24
- const { rebuildSystemPrompt } = await import("../util/config.js");
24
+ const { rebuildSystemPrompt } = await import("../core/config/index.js");
25
25
  const { getPluginPromptAdditions } = await import("../core/plugin/index.js");
26
26
  rebuildSystemPrompt(config, getPluginPromptAdditions());
27
27
 
package/src/cli/config.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * CLI config model + load/save/format helpers, plus the banner.
3
3
  *
4
4
  * This is the on-disk `talon.json` shape as the CLI sees it; the runtime has
5
- * its own richer config type in util/config.ts.
5
+ * its own richer config type in core/config/index.ts.
6
6
  */
7
7
 
8
8
  import pc from "picocolors";
@@ -20,7 +20,7 @@ export function printBanner(): void {
20
20
 
21
21
  /**
22
22
  * The subset of ~/.talon/config.json the CLI reads or prompts for — a
23
- * partial view, not the whole schema (that lives in util/config.ts).
23
+ * partial view, not the whole schema (that lives in core/config/index.ts).
24
24
  * Anything absent here is still present at runtime on a loaded config and
25
25
  * MUST be carried through on save: `talon setup` used to rebuild the file
26
26
  * from its own named fields alone and silently dropped every other key.
package/src/cli/doctor.ts CHANGED
@@ -19,7 +19,7 @@ const DOCTOR_ICONS: Record<string, string> = {
19
19
  export async function runDoctor(): Promise<void> {
20
20
  printBanner();
21
21
  console.log(` ${pc.bold("Environment check")}\n`);
22
- const { collectDoctorReport } = await import("../core/doctor.js");
22
+ const { collectDoctorReport } = await import("../core/doctor/index.js");
23
23
  // Doctor composes each backend's own checks off the registry, and the
24
24
  // CLI runs standalone — nothing else has registered them yet.
25
25
  const { loadBuiltinBackends } = await import("../backend/builtins.js");
package/src/cli/index.ts CHANGED
@@ -34,6 +34,7 @@ import { showTasks, killTask } from "./tasks.js";
34
34
  import { showEvents } from "./events.js";
35
35
  import { runPluginCommand } from "./plugin.js";
36
36
  import { runSkillCommand } from "./skill.js";
37
+ import { runMemoryCommand } from "./memory.js";
37
38
  import { mainMenu } from "./menu.js";
38
39
 
39
40
  export * from "./context.js";
@@ -56,6 +57,7 @@ const CLI_COMMANDS = [
56
57
  "events",
57
58
  "plugin",
58
59
  "skill",
60
+ "memory",
59
61
  ];
60
62
 
61
63
  /** Route a `talon <command>` invocation. Called by the entry point. */
@@ -121,6 +123,9 @@ export async function runCli(): Promise<void> {
121
123
  case "skill":
122
124
  await runSkillCommand(process.argv.slice(3));
123
125
  break;
126
+ case "memory":
127
+ runMemoryCommand(process.argv.slice(3));
128
+ break;
124
129
  case "--version":
125
130
  case "-v": {
126
131
  console.log(pkg.version);
@@ -151,6 +156,9 @@ export async function runCli(): Promise<void> {
151
156
  console.log(
152
157
  ` ${pc.cyan("skill")} Manage skills (install/enable/disable)`,
153
158
  );
159
+ console.log(
160
+ ` ${pc.cyan("memory")} Read/edit the memory store (list/search/remember)`,
161
+ );
154
162
  console.log(` ${pc.cyan("config")} View/edit configuration`);
155
163
  console.log(` ${pc.cyan("logs")} Tail log file`);
156
164
  console.log(` ${pc.cyan("doctor")} Validate environment`);
@@ -0,0 +1,166 @@
1
+ /**
2
+ * `talon memory` — read and edit the typed memory store from a terminal.
3
+ *
4
+ * All lifecycle logic lives in storage/memory.ts; this module only parses
5
+ * argv and renders rows. One row per line, in the store's own
6
+ * `formatMemory` shape, so CLI output and the `/memory` surfaces read
7
+ * identically.
8
+ */
9
+
10
+ import pc from "picocolors";
11
+ import {
12
+ assertMemory,
13
+ dropMemory,
14
+ formatMemory,
15
+ getMemory,
16
+ isMemoryKind,
17
+ listMemories,
18
+ memoryHistory,
19
+ replaceStateKey,
20
+ searchMemories,
21
+ MEMORY_KINDS,
22
+ type MemoryKind,
23
+ type MemoryRow,
24
+ } from "../storage/memory.js";
25
+
26
+ const USAGE = [
27
+ ` Usage: ${pc.cyan("talon memory <command>")}`,
28
+ "",
29
+ " Commands:",
30
+ ` ${pc.cyan("list [kind]")} Show live memories, ranked`,
31
+ ` ${pc.cyan("search <query>")} Full-text search`,
32
+ ` ${pc.cyan("show <id>")} One row plus its history`,
33
+ ` ${pc.cyan("remember <kind> <subject> <text>")} Record an operator claim`,
34
+ ` ${pc.cyan("forget <id> [reason]")} Drop a row to the graveyard`,
35
+ ` ${pc.cyan("state <key> <text>")} Replace the row for a state key`,
36
+ "",
37
+ ` Kinds: ${MEMORY_KINDS.join(", ")}`,
38
+ "",
39
+ ].join("\n");
40
+
41
+ function fail(message: string): void {
42
+ console.log(` ${pc.red("✖")} ${message}`);
43
+ }
44
+
45
+ function printRows(rows: readonly MemoryRow[], empty: string): void {
46
+ if (rows.length === 0) {
47
+ console.log(` ${pc.dim(empty)}\n`);
48
+ return;
49
+ }
50
+ for (const row of rows) console.log(` ${formatMemory(row)}`);
51
+ console.log();
52
+ }
53
+
54
+ function parseKind(value: string | undefined): MemoryKind | undefined {
55
+ if (value === undefined) return undefined;
56
+ if (!isMemoryKind(value)) throw new Error(`Unknown kind "${value}"`);
57
+ return value;
58
+ }
59
+
60
+ function cmdList(kindArg: string | undefined): void {
61
+ const kind = parseKind(kindArg);
62
+ const rows = listMemories(kind ? { kind } : {});
63
+ printRows(rows, kind ? `No ${kind} memories yet.` : "No memories yet.");
64
+ }
65
+
66
+ function cmdSearch(query: string): void {
67
+ printRows(searchMemories(query), `No memories matching "${query}".`);
68
+ }
69
+
70
+ function cmdShow(idArg: string | undefined): void {
71
+ const id = Number(idArg);
72
+ if (!Number.isInteger(id)) {
73
+ fail("show needs a numeric memory id");
74
+ return;
75
+ }
76
+ const row = getMemory(id);
77
+ if (!row) {
78
+ fail(`No memory with id ${id}`);
79
+ return;
80
+ }
81
+ console.log(` ${formatMemory(row)}`);
82
+ console.log(
83
+ ` ${pc.dim(`trust=${row.trust} confidence=${row.confidence} hits=${row.hitCount} salience=${row.salience}`)}`,
84
+ );
85
+ for (const entry of memoryHistory(id)) {
86
+ const when = new Date(entry.at).toISOString();
87
+ const why = entry.reason ? ` — ${entry.reason}` : "";
88
+ console.log(` ${pc.dim(when)} ${entry.op}${why}`);
89
+ }
90
+ console.log();
91
+ }
92
+
93
+ function cmdRemember(args: readonly string[]): void {
94
+ const [kindArg, subject, ...rest] = args;
95
+ const text = rest.join(" ");
96
+ if (!kindArg || !subject || !text) {
97
+ fail("remember needs <kind> <subject> <text...>");
98
+ return;
99
+ }
100
+ const kind = parseKind(kindArg);
101
+ const { id, similar } = assertMemory({
102
+ kind: kind!,
103
+ subject,
104
+ text,
105
+ trust: "operator",
106
+ });
107
+ console.log(` ${pc.green("●")} Remembered as #${id}`);
108
+ if (similar.length > 0) {
109
+ console.log(` ${pc.dim("Near-duplicates already stored:")}`);
110
+ for (const row of similar) console.log(` ${formatMemory(row)}`);
111
+ }
112
+ console.log();
113
+ }
114
+
115
+ function cmdForget(args: readonly string[]): void {
116
+ const id = Number(args[0]);
117
+ if (!Number.isInteger(id)) {
118
+ fail("forget needs a numeric memory id");
119
+ return;
120
+ }
121
+ const reason = args.slice(1).join(" ");
122
+ dropMemory(id, reason || undefined);
123
+ console.log(` ${pc.green("●")} Dropped #${id} to the graveyard\n`);
124
+ }
125
+
126
+ function cmdState(args: readonly string[]): void {
127
+ const [key, ...rest] = args;
128
+ const text = rest.join(" ");
129
+ if (!key || !text) {
130
+ fail("state needs <key> <text...>");
131
+ return;
132
+ }
133
+ const id = replaceStateKey(key, text);
134
+ console.log(` ${pc.green("●")} ${key} is now #${id}\n`);
135
+ }
136
+
137
+ /** Route a `talon memory <command>` invocation. */
138
+ export function runMemoryCommand(args: readonly string[]): void {
139
+ try {
140
+ switch (args[0]) {
141
+ case "list":
142
+ cmdList(args[1]);
143
+ break;
144
+ case "search":
145
+ if (!args[1]) fail("search needs a query");
146
+ else cmdSearch(args.slice(1).join(" "));
147
+ break;
148
+ case "show":
149
+ cmdShow(args[1]);
150
+ break;
151
+ case "remember":
152
+ cmdRemember(args.slice(1));
153
+ break;
154
+ case "forget":
155
+ cmdForget(args.slice(1));
156
+ break;
157
+ case "state":
158
+ cmdState(args.slice(1));
159
+ break;
160
+ default:
161
+ console.log(USAGE);
162
+ }
163
+ } catch (err) {
164
+ fail(err instanceof Error ? err.message : String(err));
165
+ }
166
+ }
package/src/cli/setup.ts CHANGED
@@ -650,7 +650,7 @@ export function buildSetupConfig(
650
650
  : existing.discord,
651
651
  // Never dropped. Deselecting WhatsApp shouldn't throw away its
652
652
  // pairing setup, and dropping it while `frontend` still lists
653
- // whatsapp produces a config util/config.ts refuses to load at all
653
+ // whatsapp produces a config core/config/index.ts refuses to load at all
654
654
  // ("WhatsApp frontend requires a whatsapp config block") — a daemon
655
655
  // that won't start. The `??` keeps that impossible even if the
656
656
  // branch that collects the answer is ever skipped.
@@ -30,8 +30,8 @@
30
30
  */
31
31
 
32
32
  import type { Backend } from "./capabilities.js";
33
- import type { DoctorCheck, DoctorConfigSlice } from "../doctor-types.js";
34
- import type { TalonConfig } from "../../util/config.js";
33
+ import type { DoctorCheck, DoctorConfigSlice } from "../doctor/types.js";
34
+ import type { TalonConfig } from "../config/index.js";
35
35
 
36
36
  // ── Types ───────────────────────────────────────────────────────────────────
37
37
 
@@ -13,7 +13,7 @@
13
13
  * logs, telemetry, tests) doesn't have to chase it down again.
14
14
  *
15
15
  * This module also pins the canonical `BackendId` union;
16
- * `src/util/config.ts` imports `BACKEND_IDS` directly for its zod
16
+ * `src/core/config/index.ts` imports `BACKEND_IDS` directly for its zod
17
17
  * enums. Adding a backend means updating THIS literal once; the
18
18
  * config schema picks the change up automatically. Don't
19
19
  * reintroduce a manually-mirrored zod tuple — it's a footgun.
@@ -24,7 +24,7 @@ import type { ReasoningEffortLevel } from "../types.js";
24
24
  /**
25
25
  * Canonical backend identifiers — single source of truth for the
26
26
  * typed `BackendId` union AND the runtime zod enums in
27
- * `src/util/config.ts`. Update one place, everything stays in
27
+ * `src/core/config/index.ts`. Update one place, everything stays in
28
28
  * lockstep.
29
29
  */
30
30
  export const BACKEND_IDS = [
@@ -9,7 +9,7 @@
9
9
  * fresh login clears it, so the next expiry warns again).
10
10
  */
11
11
 
12
- import { notifyAdmin } from "../notify.js";
12
+ import { notifyAdmin } from "../frontend-runtime/admin-notify.js";
13
13
  import {
14
14
  daysUntil,
15
15
  PROVIDER_LABELS,
@@ -1,16 +1,16 @@
1
1
  import { existsSync, readFileSync, mkdirSync } from "node:fs";
2
2
  import writeFileAtomic from "write-file-atomic";
3
3
  import { z } from "zod";
4
- import { dirs, files as pathFiles } from "./paths.js";
5
- import { hardenTalonPermissions } from "./harden.js";
6
- import { setTimezone } from "./time.js";
7
- import { BACKEND_IDS } from "../core/agent-runtime/model-ref.js";
8
- import { REASONING_LEVEL_ORDER } from "../core/models/reasoning-levels.js";
4
+ import { dirs, files as pathFiles } from "../../util/paths.js";
5
+ import { hardenTalonPermissions } from "../../util/harden.js";
6
+ import { setTimezone } from "../../util/time.js";
7
+ import { BACKEND_IDS } from "../agent-runtime/model-ref.js";
8
+ import { REASONING_LEVEL_ORDER } from "../models/reasoning-levels.js";
9
9
  import {
10
10
  assembleSystemPrompt,
11
11
  joinSystemPromptParts,
12
12
  type SystemPromptParts,
13
- } from "../core/prompt/assemble.js";
13
+ } from "../prompt/assemble.js";
14
14
 
15
15
  /**
16
16
  * Backend-id literal source.
@@ -609,11 +609,11 @@ const configSchema = z.object({
609
609
 
610
610
  // System-prompt assembly lives in core/prompt/ (section pipeline,
611
611
  // templates, workspace listing). Re-exported here so existing
612
- // consumers keep importing from util/config.
612
+ // consumers keep importing from core/config.
613
613
  export {
614
614
  joinSystemPromptParts,
615
615
  type SystemPromptParts,
616
- } from "../core/prompt/assemble.js";
616
+ } from "../prompt/assemble.js";
617
617
 
618
618
  export type TalonConfig = z.infer<typeof configSchema> & {
619
619
  systemPrompt: string;
@@ -13,22 +13,22 @@
13
13
 
14
14
  import { existsSync } from "node:fs";
15
15
  import { stat } from "node:fs/promises";
16
- import { NATIVE_MODULES } from "../native/registry.js";
17
- import { dirs } from "../util/paths.js";
18
- import { getBackend, listBackends } from "./agent-runtime/backend-registry.js";
16
+ import { NATIVE_MODULES } from "../../native/registry.js";
17
+ import { dirs } from "../../util/paths.js";
18
+ import { getBackend, listBackends } from "../agent-runtime/backend-registry.js";
19
19
  import type {
20
20
  DoctorCheck,
21
21
  DoctorConfigSlice,
22
22
  DoctorReport,
23
23
  NativeModuleCheck,
24
- } from "./doctor-types.js";
24
+ } from "./types.js";
25
25
 
26
26
  export type {
27
27
  DoctorCheck,
28
28
  DoctorConfigSlice,
29
29
  DoctorReport,
30
30
  NativeModuleCheck,
31
- } from "./doctor-types.js";
31
+ } from "./types.js";
32
32
 
33
33
  function errorNote(err: unknown): string {
34
34
  return err instanceof Error ? err.message : String(err);
@@ -180,7 +180,7 @@ async function checkPluginRuntimes(
180
180
  config: DoctorConfigSlice | undefined,
181
181
  ): Promise<DoctorCheck[]> {
182
182
  const checks: DoctorCheck[] = [];
183
- const { NATIVE_RUNTIMES } = await import("./plugin/native-runtimes.js");
183
+ const { NATIVE_RUNTIMES } = await import("../plugin/native-runtimes.js");
184
184
  for (const runtime of NATIVE_RUNTIMES) {
185
185
  if (!runtime.enabled(config)) continue;
186
186
  try {
@@ -322,7 +322,7 @@ export async function collectDoctorReport(opts: {
322
322
  // on the in-process TS path. When present, report the live version.
323
323
  {
324
324
  const { wardenBinaryPath, wardenVersion } =
325
- await import("../native/warden.js");
325
+ await import("../../native/warden.js");
326
326
  const wardenBin = wardenBinaryPath();
327
327
  if (wardenBin) {
328
328
  const version = wardenVersion();
@@ -350,7 +350,7 @@ export async function collectDoctorReport(opts: {
350
350
  // and on the event loop), so it's informational, not an issue. The
351
351
  // addon was digest-verified at load time by nativeBlake3().
352
352
  {
353
- const { nativeBlake3 } = await import("../native/blake3.js");
353
+ const { nativeBlake3 } = await import("../../native/blake3.js");
354
354
  const addon = nativeBlake3();
355
355
  checks.push(
356
356
  addon
@@ -370,7 +370,7 @@ export async function collectDoctorReport(opts: {
370
370
  // ~/.talon/ns is the symlink farm only (file mounts fine, no live
371
371
  // proc//plugins views), so it's informational, not an issue.
372
372
  {
373
- const { nativeFuseFs } = await import("../native/fusefs.js");
373
+ const { nativeFuseFs } = await import("../../native/fusefs.js");
374
374
  const addon = nativeFuseFs();
375
375
  checks.push(
376
376
  addon
@@ -392,7 +392,7 @@ export async function collectDoctorReport(opts: {
392
392
  // stale or the package is incomplete — caught at check time, not
393
393
  // mid-message when a backend assembles its system prompt.
394
394
  {
395
- const { loadSystemTemplate } = await import("./prompt/index.js");
395
+ const { loadSystemTemplate } = await import("../prompt/index.js");
396
396
  try {
397
397
  const sample = loadSystemTemplate("workspace");
398
398
  checks.push(
@@ -423,7 +423,7 @@ export async function collectDoctorReport(opts: {
423
423
  // is which is the difference between "why didn't my prompt update?"
424
424
  // and a one-line answer.
425
425
  {
426
- const { promptSeedReport } = await import("../util/workspace.js");
426
+ const { promptSeedReport } = await import("../../util/workspace.js");
427
427
  try {
428
428
  const { tracking, edited } = promptSeedReport();
429
429
  if (tracking.length + edited.length > 0) {
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { Backend } from "../../agent-runtime/capabilities.js";
9
- import type { TalonConfig } from "../../../util/config.js";
9
+ import type { TalonConfig } from "../../config/index.js";
10
10
  import { type BackendInitContext } from "../../agent-runtime/backend-registry.js";
11
11
  import { log } from "../../../util/log.js";
12
12
  import { roleHolder } from "./holders.js";
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { Backend } from "../../agent-runtime/capabilities.js";
8
- import type { TalonConfig } from "../../../util/config.js";
8
+ import type { TalonConfig } from "../../config/index.js";
9
9
  import {
10
10
  listBackends,
11
11
  type BackendInitContext,
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { Backend } from "../../agent-runtime/capabilities.js";
7
- import type { TalonConfig } from "../../../util/config.js";
7
+ import type { TalonConfig } from "../../config/index.js";
8
8
  import {
9
9
  getBackend,
10
10
  listBackends,
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { Backend } from "../../agent-runtime/capabilities.js";
12
- import type { TalonConfig } from "../../../util/config.js";
12
+ import type { TalonConfig } from "../../config/index.js";
13
13
  import {
14
14
  getBackend,
15
15
  listBackends,
@@ -21,7 +21,7 @@ export async function performPluginReload(
21
21
  ): Promise<{ names: string[] }> {
22
22
  const { reloadPlugins, getPluginPromptAdditions } =
23
23
  await import("../../plugin/index.js");
24
- const { rebuildSystemPrompt } = await import("../../../util/config.js");
24
+ const { rebuildSystemPrompt } = await import("../../config/index.js");
25
25
  const { notifyPromptInputsChanged } =
26
26
  await import("../../prompt/invalidation.js");
27
27