talon-agent 3.0.5 → 3.1.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.
- package/package.json +6 -1
- package/src/backend/kilo/models/index.ts +4 -1
- package/src/backend/kilo/server.ts +17 -4
- package/src/backend/openai-agents/session.ts +1 -1
- package/src/backend/opencode/models/index.ts +4 -1
- package/src/backend/opencode/server.ts +17 -4
- package/src/backend/shared/system-prompt.ts +5 -0
- package/src/cli/events.ts +1 -1
- package/src/cli/tasks.ts +5 -1
- package/src/core/background/triggers/pid.ts +28 -0
- package/src/core/background/triggers/resume.ts +4 -24
- package/src/core/background/triggers/spawn.ts +1 -1
- package/src/core/engine/gateway-actions/plugins.ts +3 -3
- package/src/core/prompt/invalidation.ts +23 -0
- package/src/core/types.ts +2 -2
- package/src/frontend/discord/callbacks/components.ts +1 -1
- package/src/frontend/discord/commands/settings.ts +1 -1
- package/src/frontend/native/extensions.ts +2 -2
- package/src/frontend/telegram/callbacks/model.ts +1 -1
- package/src/frontend/telegram/commands/settings.ts +1 -1
- package/src/frontend/terminal/commands.ts +1 -1
- package/src/plugins/github/index.ts +1 -1
- package/src/plugins/mem0/index.ts +1 -1
- package/src/plugins/mempalace/index.ts +1 -1
- package/src/plugins/playwright/index.ts +1 -1
- package/src/storage/chat-settings.ts +3 -60
- package/src/storage/cron-store.ts +6 -80
- package/src/storage/goal-store.ts +10 -19
- package/src/storage/history.ts +2 -13
- package/src/storage/journal.ts +20 -9
- package/src/storage/media-index.ts +2 -22
- package/src/storage/repositories/chat-settings-repo.ts +55 -1
- package/src/storage/repositories/cron-repo.ts +82 -6
- package/src/storage/repositories/goals-repo.ts +20 -1
- package/src/storage/repositories/history-repo.ts +14 -1
- package/src/storage/repositories/media-index-repo.ts +23 -1
- package/src/storage/repositories/scripts-repo.ts +16 -1
- package/src/storage/repositories/sessions-repo.ts +1 -1
- package/src/storage/repositories/triggers-repo.ts +60 -5
- package/src/storage/script-store.ts +2 -15
- package/src/storage/session-record.ts +220 -0
- package/src/storage/sessions.ts +22 -210
- package/src/storage/trigger-store.ts +6 -60
- package/src/types/assets.d.ts +9 -0
- package/src/types/effort.ts +12 -0
- package/tsconfig.json +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "talon-agent",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.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",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"src/native/",
|
|
43
43
|
"src/plugins/",
|
|
44
44
|
"src/storage/",
|
|
45
|
+
"src/types/",
|
|
45
46
|
"src/util/",
|
|
46
47
|
"src/app.ts",
|
|
47
48
|
"src/bootstrap.ts",
|
|
@@ -77,6 +78,8 @@
|
|
|
77
78
|
"test:coverage": "vitest run --coverage",
|
|
78
79
|
"typecheck": "tsc --noEmit",
|
|
79
80
|
"lint": "oxlint src/",
|
|
81
|
+
"depcruise": "depcruise src",
|
|
82
|
+
"ratchets": "node scripts/check-ratchets.mjs",
|
|
80
83
|
"knip": "knip",
|
|
81
84
|
"format": "prettier --write src/ prompts/",
|
|
82
85
|
"format:check": "prettier --check src/ prompts/",
|
|
@@ -130,9 +133,11 @@
|
|
|
130
133
|
"zod": "^4.3.6"
|
|
131
134
|
},
|
|
132
135
|
"devDependencies": {
|
|
136
|
+
"@swc/core": "^1.15.46",
|
|
133
137
|
"@types/node": "^26.0.0",
|
|
134
138
|
"@types/write-file-atomic": "^4.0.3",
|
|
135
139
|
"@vitest/coverage-v8": "^4.1.3",
|
|
140
|
+
"dependency-cruiser": "^18.1.0",
|
|
136
141
|
"fast-check": "^4.6.0",
|
|
137
142
|
"knip": "^6.3.1",
|
|
138
143
|
"oxlint": "^1.59.0",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Kilo ids routinely contain "/" and ":" (e.g. "inclusionai/ling-2.6-1t:free").
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { ensureServer } from "../server.js";
|
|
16
|
+
import { ensureServer, onServerStop } from "../server.js";
|
|
17
17
|
import { createRemoteModelCatalogModule } from "../../remote-server/model-catalog/index.js";
|
|
18
18
|
|
|
19
19
|
export type {
|
|
@@ -41,6 +41,9 @@ const kiloModels = createRemoteModelCatalogModule({
|
|
|
41
41
|
quickPickLimit: 24,
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
// A stopped server invalidates the catalog it served.
|
|
45
|
+
onServerStop(kiloModels.clearCache);
|
|
46
|
+
|
|
44
47
|
export const getOpenCodeModelCatalog = kiloModels.getCatalog;
|
|
45
48
|
export const clearModelCatalogCache = kiloModels.clearCache;
|
|
46
49
|
export const getOpenCodeModelInfo = kiloModels.getModelInfo;
|
|
@@ -25,13 +25,12 @@ import type { TalonConfig } from "../../util/config.js";
|
|
|
25
25
|
import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
|
|
26
26
|
import { logWarn } from "../../util/log.js";
|
|
27
27
|
import { buildDeliveryContract } from "../shared/delivery-contract.js";
|
|
28
|
-
import { clearModelCatalogCache } from "./models/index.js";
|
|
29
28
|
import {
|
|
30
29
|
guessProviderID,
|
|
31
30
|
getBucketPriority,
|
|
32
31
|
normalizeModelLookup,
|
|
33
|
-
parseOpenCodeModelQuery,
|
|
34
|
-
} from "
|
|
32
|
+
parseRemoteModelQuery as parseOpenCodeModelQuery,
|
|
33
|
+
} from "../remote-server/model-catalog/index.js";
|
|
35
34
|
import {
|
|
36
35
|
createRemoteServerState,
|
|
37
36
|
ensureRemoteServer as ensureRemoteServerShared,
|
|
@@ -153,6 +152,18 @@ async function prewarmPluginMcpServers(): Promise<void> {
|
|
|
153
152
|
await ensurePluginMcpServers(client, "prewarm");
|
|
154
153
|
}
|
|
155
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Callbacks run when the server stops — cache invalidation lives with the
|
|
157
|
+
* caches. The models module registers its clear here at load time, so the
|
|
158
|
+
* server never has to import it (which would be a cycle).
|
|
159
|
+
*/
|
|
160
|
+
const stopHooks = new Set<() => void>();
|
|
161
|
+
|
|
162
|
+
/** Register a callback to run whenever the Kilo server is stopped. */
|
|
163
|
+
export function onServerStop(hook: () => void): void {
|
|
164
|
+
stopHooks.add(hook);
|
|
165
|
+
}
|
|
166
|
+
|
|
156
167
|
/**
|
|
157
168
|
* Stop the local Kilo server (if we own it) and clear caches.
|
|
158
169
|
*
|
|
@@ -160,7 +171,9 @@ async function prewarmPluginMcpServers(): Promise<void> {
|
|
|
160
171
|
* server, this leaves it running — we don't own it.
|
|
161
172
|
*/
|
|
162
173
|
export function stopKiloServer(): void {
|
|
163
|
-
stopRemoteServer(state,
|
|
174
|
+
stopRemoteServer(state, () => {
|
|
175
|
+
for (const hook of stopHooks) hook();
|
|
176
|
+
});
|
|
164
177
|
}
|
|
165
178
|
|
|
166
179
|
// ── Server lifecycle ────────────────────────────────────────────────────────
|
|
@@ -251,7 +251,7 @@ export class TalonSession extends MemorySession {
|
|
|
251
251
|
return current;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
async addItems(items: AgentInputItem[]): Promise<void> {
|
|
254
|
+
override async addItems(items: AgentInputItem[]): Promise<void> {
|
|
255
255
|
await super.addItems(items);
|
|
256
256
|
await this.enforceCap();
|
|
257
257
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* stay at 4 and only short separator-free ids are embedded raw.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { ensureServer } from "../server.js";
|
|
15
|
+
import { ensureServer, onServerStop } from "../server.js";
|
|
16
16
|
import { createRemoteModelCatalogModule } from "../../remote-server/model-catalog/index.js";
|
|
17
17
|
|
|
18
18
|
export type {
|
|
@@ -40,6 +40,9 @@ const opencodeModels = createRemoteModelCatalogModule({
|
|
|
40
40
|
quickPickLimit: 4,
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
// A stopped server invalidates the catalog it served.
|
|
44
|
+
onServerStop(opencodeModels.clearCache);
|
|
45
|
+
|
|
43
46
|
export const getOpenCodeModelCatalog = opencodeModels.getCatalog;
|
|
44
47
|
export const clearModelCatalogCache = opencodeModels.clearCache;
|
|
45
48
|
export const getOpenCodeModelInfo = opencodeModels.getModelInfo;
|
|
@@ -25,13 +25,12 @@ import type { TalonConfig } from "../../util/config.js";
|
|
|
25
25
|
import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
|
|
26
26
|
import { logWarn } from "../../util/log.js";
|
|
27
27
|
import { buildDeliveryContract } from "../shared/delivery-contract.js";
|
|
28
|
-
import { clearModelCatalogCache } from "./models/index.js";
|
|
29
28
|
import {
|
|
30
29
|
guessProviderID,
|
|
31
30
|
getBucketPriority,
|
|
32
31
|
normalizeModelLookup,
|
|
33
|
-
parseOpenCodeModelQuery,
|
|
34
|
-
} from "
|
|
32
|
+
parseRemoteModelQuery as parseOpenCodeModelQuery,
|
|
33
|
+
} from "../remote-server/model-catalog/index.js";
|
|
35
34
|
import {
|
|
36
35
|
createRemoteServerState,
|
|
37
36
|
ensureRemoteServer as ensureRemoteServerShared,
|
|
@@ -108,8 +107,22 @@ async function prewarmPluginMcpServers(): Promise<void> {
|
|
|
108
107
|
await ensurePluginMcpServers(client, "prewarm");
|
|
109
108
|
}
|
|
110
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Callbacks run when the server stops — cache invalidation lives with the
|
|
112
|
+
* caches. The models module registers its clear here at load time, so the
|
|
113
|
+
* server never has to import it (which would be a cycle).
|
|
114
|
+
*/
|
|
115
|
+
const stopHooks = new Set<() => void>();
|
|
116
|
+
|
|
117
|
+
/** Register a callback to run whenever the OpenCode server is stopped. */
|
|
118
|
+
export function onServerStop(hook: () => void): void {
|
|
119
|
+
stopHooks.add(hook);
|
|
120
|
+
}
|
|
121
|
+
|
|
111
122
|
export function stopOpenCodeServer(): void {
|
|
112
|
-
stopRemoteServer(state,
|
|
123
|
+
stopRemoteServer(state, () => {
|
|
124
|
+
for (const hook of stopHooks) hook();
|
|
125
|
+
});
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
// ── Server lifecycle ────────────────────────────────────────────────────────
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
type TalonConfig,
|
|
42
42
|
} from "../../util/config.js";
|
|
43
43
|
import { getPluginPromptAdditions } from "../../core/plugin/index.js";
|
|
44
|
+
import { onPromptInputsChanged } from "../../core/prompt/invalidation.js";
|
|
44
45
|
import { frontendForChatId, nonTerminalFrontends } from "./frontends.js";
|
|
45
46
|
|
|
46
47
|
// ── Types ───────────────────────────────────────────────────────────────────
|
|
@@ -112,6 +113,10 @@ export function clearSystemPromptSnapshots(): void {
|
|
|
112
113
|
snapshots.clear();
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
// Core and frontends invalidate through the core-side seam — they never
|
|
117
|
+
// import this module (layer rule: nothing above backend/ reaches into it).
|
|
118
|
+
onPromptInputsChanged(clearSystemPromptSnapshots);
|
|
119
|
+
|
|
115
120
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
116
121
|
|
|
117
122
|
/**
|
package/src/cli/events.ts
CHANGED
|
@@ -52,7 +52,7 @@ async function showHistory(limit: number): Promise<void> {
|
|
|
52
52
|
const { readJournal } = await import("../storage/journal.js");
|
|
53
53
|
let entries;
|
|
54
54
|
try {
|
|
55
|
-
entries = readJournal({ limit });
|
|
55
|
+
entries = readJournal<TalonEvent & { at: number }>({ limit });
|
|
56
56
|
} catch (err) {
|
|
57
57
|
console.log(
|
|
58
58
|
` ${pc.red("✖")} Could not read the journal: ${err instanceof Error ? err.message : err}\n`,
|
package/src/cli/tasks.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
TaskRecord,
|
|
17
17
|
TaskState,
|
|
18
18
|
} from "../core/tasks/index.js";
|
|
19
|
+
import type { PublishedEvent } from "../core/bus/index.js";
|
|
19
20
|
|
|
20
21
|
function formatDuration(ms: number): string {
|
|
21
22
|
const seconds = Math.floor(ms / 1000);
|
|
@@ -120,7 +121,10 @@ async function journalTasks(
|
|
|
120
121
|
const { readJournal } = await import("../storage/journal.js");
|
|
121
122
|
const seen = new Set(liveTasks.map((t) => `${t.id}:${t.queuedAt}`));
|
|
122
123
|
const historical: TaskRecord[] = [];
|
|
123
|
-
for (const entry of readJournal({
|
|
124
|
+
for (const entry of readJournal<PublishedEvent>({
|
|
125
|
+
type: "task.settled",
|
|
126
|
+
limit,
|
|
127
|
+
})) {
|
|
124
128
|
if (entry.event.type !== "task.settled") continue;
|
|
125
129
|
const task = entry.event.task;
|
|
126
130
|
// (id, queuedAt) identifies a run across surfaces — per-process ids
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PID-starttime probe — the /proc read shared by spawn (capture at fork)
|
|
3
|
+
* and resume (compare after restart). Lives alone so neither module has
|
|
4
|
+
* to import the other for it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Read field 22 (start time in jiffies since boot) from /proc/<pid>/stat.
|
|
11
|
+
* Returns undefined if /proc isn't available (non-Linux) or the read fails.
|
|
12
|
+
*
|
|
13
|
+
* Parsing note: the `comm` field (2nd) is wrapped in parens and may itself
|
|
14
|
+
* contain ')' — the safe parse finds the LAST ')' and splits the rest on
|
|
15
|
+
* space. After that split, index 19 corresponds to field 22.
|
|
16
|
+
*/
|
|
17
|
+
export function readPidStarttimeSync(pid: number): number | undefined {
|
|
18
|
+
try {
|
|
19
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf-8");
|
|
20
|
+
const lastParen = stat.lastIndexOf(")");
|
|
21
|
+
if (lastParen < 0) return undefined;
|
|
22
|
+
const tail = stat.slice(lastParen + 2).split(" ");
|
|
23
|
+
const starttime = Number(tail[19]);
|
|
24
|
+
return Number.isFinite(starttime) ? starttime : undefined;
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Post-restart resume — respawn persistent triggers, fire late wakes for
|
|
3
|
-
* recently-terminated ones, plus the
|
|
4
|
-
*
|
|
3
|
+
* recently-terminated ones, plus the orphan-kill used to avoid duplicate
|
|
4
|
+
* spawns after an unclean crash. The /proc PID-starttime probe both this
|
|
5
|
+
* and spawn use lives in ./pid.ts.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import { readFileSync } from "node:fs";
|
|
8
8
|
import {
|
|
9
9
|
getAllTriggers,
|
|
10
10
|
updateTrigger,
|
|
@@ -15,6 +15,7 @@ import { log, logError } from "../../../util/log.js";
|
|
|
15
15
|
import { depsHolder } from "./state.js";
|
|
16
16
|
import { fireWake } from "./output.js";
|
|
17
17
|
import { spawnTrigger } from "./spawn.js";
|
|
18
|
+
import { readPidStarttimeSync } from "./pid.js";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* After the dispatcher is wired, walk the store and clean up leftover state
|
|
@@ -68,27 +69,6 @@ export async function resumeAfterRestart(): Promise<void> {
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
/**
|
|
72
|
-
* Read field 22 (start time in jiffies since boot) from /proc/<pid>/stat.
|
|
73
|
-
* Returns undefined if /proc isn't available (non-Linux) or the read fails.
|
|
74
|
-
*
|
|
75
|
-
* Parsing note: the `comm` field (2nd) is wrapped in parens and may itself
|
|
76
|
-
* contain ')' — the safe parse finds the LAST ')' and splits the rest on
|
|
77
|
-
* space. After that split, index 19 corresponds to field 22.
|
|
78
|
-
*/
|
|
79
|
-
export function readPidStarttimeSync(pid: number): number | undefined {
|
|
80
|
-
try {
|
|
81
|
-
const stat = readFileSync(`/proc/${pid}/stat`, "utf-8");
|
|
82
|
-
const lastParen = stat.lastIndexOf(")");
|
|
83
|
-
if (lastParen < 0) return undefined;
|
|
84
|
-
const tail = stat.slice(lastParen + 2).split(" ");
|
|
85
|
-
const starttime = Number(tail[19]);
|
|
86
|
-
return Number.isFinite(starttime) ? starttime : undefined;
|
|
87
|
-
} catch {
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
72
|
/**
|
|
93
73
|
* Probe a stored PID from a previous Talon run and SIGKILL it if it's still
|
|
94
74
|
* alive AND really is our former child (not a recycled PID). Used by
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import { commandForLanguage } from "./command.js";
|
|
26
26
|
import { handleStdoutLine, handleStderrLine } from "./output.js";
|
|
27
27
|
import { handleTimeout, finalizeExit, failTrigger } from "./exit.js";
|
|
28
|
-
import { readPidStarttimeSync } from "./
|
|
28
|
+
import { readPidStarttimeSync } from "./pid.js";
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Spawn a trigger's script as a supervised child process.
|
|
@@ -22,8 +22,8 @@ export async function performPluginReload(
|
|
|
22
22
|
const { reloadPlugins, getPluginPromptAdditions } =
|
|
23
23
|
await import("../../plugin/index.js");
|
|
24
24
|
const { rebuildSystemPrompt } = await import("../../../util/config.js");
|
|
25
|
-
const {
|
|
26
|
-
await import("
|
|
25
|
+
const { notifyPromptInputsChanged } =
|
|
26
|
+
await import("../../prompt/invalidation.js");
|
|
27
27
|
|
|
28
28
|
// reloadPlugins reads + validates config internally — no double read.
|
|
29
29
|
// Frontends are derived from config if not explicitly provided.
|
|
@@ -37,7 +37,7 @@ export async function performPluginReload(
|
|
|
37
37
|
// Plugin prompt additions changed — drop per-session prompt
|
|
38
38
|
// snapshots so every chat's next turn picks up the new prompt
|
|
39
39
|
// (deliberate one-time cache re-write per live session).
|
|
40
|
-
|
|
40
|
+
notifyPromptInputsChanged();
|
|
41
41
|
|
|
42
42
|
return { names };
|
|
43
43
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt-input invalidation — the core-side seam callers use when prompt
|
|
3
|
+
* inputs change out from under live sessions on purpose (plugin reload,
|
|
4
|
+
* skill toggle).
|
|
5
|
+
*
|
|
6
|
+
* Whoever caches assembled prompts registers a hook here (today: the
|
|
7
|
+
* per-session snapshot store in backend/shared/system-prompt.ts, at its
|
|
8
|
+
* module load). Core and frontends call `notifyPromptInputsChanged()`
|
|
9
|
+
* and never import the cache — the dependency points backend → core,
|
|
10
|
+
* as the layer rule requires.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const hooks = new Set<() => void>();
|
|
14
|
+
|
|
15
|
+
/** Register a callback run whenever prompt inputs are invalidated. */
|
|
16
|
+
export function onPromptInputsChanged(hook: () => void): void {
|
|
17
|
+
hooks.add(hook);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Invalidate: every registered prompt cache drops its state. */
|
|
21
|
+
export function notifyPromptInputsChanged(): void {
|
|
22
|
+
for (const hook of hooks) hook();
|
|
23
|
+
}
|
package/src/core/types.ts
CHANGED
|
@@ -29,8 +29,8 @@ export type UnifiedModelInfo = {
|
|
|
29
29
|
unavailableReason?: string;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
export type ReasoningEffortLevel
|
|
33
|
-
|
|
32
|
+
export type { ReasoningEffortLevel } from "../types/effort.js";
|
|
33
|
+
import type { ReasoningEffortLevel } from "../types/effort.js";
|
|
34
34
|
|
|
35
35
|
/** Result of resolving a user's model query. */
|
|
36
36
|
export type UnifiedModelResolution =
|
|
@@ -34,9 +34,9 @@ import {
|
|
|
34
34
|
setChatModelForBackend,
|
|
35
35
|
setChatBackend,
|
|
36
36
|
setChatEffort,
|
|
37
|
-
resolveModelName,
|
|
38
37
|
type EffortLevel,
|
|
39
38
|
} from "../../../storage/chat-settings.js";
|
|
39
|
+
import { resolveModelId as resolveModelName } from "../../../core/models/catalog.js";
|
|
40
40
|
import {
|
|
41
41
|
registerChat,
|
|
42
42
|
disablePulse,
|
|
@@ -18,9 +18,9 @@ import {
|
|
|
18
18
|
setChatBackend,
|
|
19
19
|
setChatEffort,
|
|
20
20
|
setChatPulseInterval,
|
|
21
|
-
resolveModelName,
|
|
22
21
|
type EffortLevel,
|
|
23
22
|
} from "../../../storage/chat-settings.js";
|
|
23
|
+
import { resolveModelId as resolveModelName } from "../../../core/models/catalog.js";
|
|
24
24
|
import {
|
|
25
25
|
registerChat,
|
|
26
26
|
disablePulse,
|
|
@@ -21,7 +21,7 @@ import type { Backend } from "../../core/agent-runtime/capabilities.js";
|
|
|
21
21
|
import { performPluginReload } from "../../core/engine/gateway-actions/plugins.js";
|
|
22
22
|
import { getPluginPromptAdditions } from "../../core/plugin/index.js";
|
|
23
23
|
import { listPluginItems, setPluginEnabled } from "../../core/plugin/manage.js";
|
|
24
|
-
import {
|
|
24
|
+
import { notifyPromptInputsChanged } from "../../core/prompt/invalidation.js";
|
|
25
25
|
import { listSkills, setSkillEnabled } from "../../storage/skill-store.js";
|
|
26
26
|
import { rebuildSystemPrompt, type TalonConfig } from "../../util/config.js";
|
|
27
27
|
import { log } from "../../util/log.js";
|
|
@@ -88,6 +88,6 @@ export function toggleSkill(
|
|
|
88
88
|
log("native", `Skill ${name} ${enabled ? "enabled" : "disabled"} via bridge`);
|
|
89
89
|
rebuildSystemPrompt(config, getPluginPromptAdditions());
|
|
90
90
|
backend?.control?.updateSystemPrompt?.(config.systemPrompt);
|
|
91
|
-
|
|
91
|
+
notifyPromptInputsChanged();
|
|
92
92
|
return { ok: true };
|
|
93
93
|
}
|
|
@@ -11,8 +11,8 @@ import type { Context } from "grammy";
|
|
|
11
11
|
import {
|
|
12
12
|
setChatModelForBackend,
|
|
13
13
|
setChatBackend,
|
|
14
|
-
resolveModelName,
|
|
15
14
|
} from "../../../storage/chat-settings.js";
|
|
15
|
+
import { resolveModelId as resolveModelName } from "../../../core/models/catalog.js";
|
|
16
16
|
import { resetSession } from "../../../storage/sessions.js";
|
|
17
17
|
import { clearHistory } from "../../../storage/history.js";
|
|
18
18
|
import {
|
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
setChatBackend,
|
|
10
10
|
setChatEffort,
|
|
11
11
|
setChatPulseInterval,
|
|
12
|
-
resolveModelName,
|
|
13
12
|
type EffortLevel,
|
|
14
13
|
} from "../../../storage/chat-settings.js";
|
|
14
|
+
import { resolveModelId as resolveModelName } from "../../../core/models/catalog.js";
|
|
15
15
|
import {
|
|
16
16
|
registerChat,
|
|
17
17
|
disablePulse,
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
getChatSettings,
|
|
21
21
|
setChatModel,
|
|
22
22
|
setChatEffort,
|
|
23
|
-
resolveModelName,
|
|
24
23
|
} from "../../storage/chat-settings.js";
|
|
24
|
+
import { resolveModelId as resolveModelName } from "../../core/models/catalog.js";
|
|
25
25
|
import {
|
|
26
26
|
getAllSessions,
|
|
27
27
|
getSession,
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { readFileSync } from "node:fs";
|
|
22
22
|
import { resolve } from "node:path";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
|
-
import type { TalonPlugin } from "../../core/plugin/
|
|
24
|
+
import type { TalonPlugin } from "../../core/plugin/types.js";
|
|
25
25
|
import { log, logWarn } from "../../util/log.js";
|
|
26
26
|
import { dirs } from "../../util/paths.js";
|
|
27
27
|
|
|
@@ -18,7 +18,7 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
|
18
18
|
import { resolve } from "node:path";
|
|
19
19
|
import { execFile as execFileCb, execFileSync } from "node:child_process";
|
|
20
20
|
import { promisify } from "node:util";
|
|
21
|
-
import type { TalonPlugin } from "../../core/plugin/
|
|
21
|
+
import type { TalonPlugin } from "../../core/plugin/types.js";
|
|
22
22
|
import { log, logWarn } from "../../util/log.js";
|
|
23
23
|
import { dirs } from "../../util/paths.js";
|
|
24
24
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import { existsSync, readFileSync } from "node:fs";
|
|
23
23
|
import { resolve } from "node:path";
|
|
24
|
-
import type { TalonPlugin } from "../../core/plugin/
|
|
24
|
+
import type { TalonPlugin } from "../../core/plugin/types.js";
|
|
25
25
|
import { log } from "../../util/log.js";
|
|
26
26
|
|
|
27
27
|
export function createPlaywrightPlugin(config: {
|
|
@@ -22,63 +22,12 @@ import { recordError } from "../util/watchdog.js";
|
|
|
22
22
|
import { files } from "../util/paths.js";
|
|
23
23
|
import { importLegacyJson } from "./legacy-import.js";
|
|
24
24
|
import * as repo from "./repositories/chat-settings-repo.js";
|
|
25
|
-
import type { ReasoningEffortLevel } from "../
|
|
25
|
+
import type { ReasoningEffortLevel } from "../types/effort.js";
|
|
26
26
|
|
|
27
27
|
export type EffortLevel = ReasoningEffortLevel;
|
|
28
28
|
|
|
29
|
-
export type ChatSettings
|
|
30
|
-
|
|
31
|
-
* Per-backend model overrides for this chat. Keyed by backend id
|
|
32
|
-
* (`"claude"`, `"codex"`, `"openai-agents"`, etc). Each entry is the
|
|
33
|
-
* model id the user picked on that backend.
|
|
34
|
-
*
|
|
35
|
-
* Switching backends preserves each side's last pick — your Codex
|
|
36
|
-
* chat remembers `gpt-5.5`, your OpenRouter chat remembers
|
|
37
|
-
* `meta-llama/...`. Replaces the single legacy `model` field which
|
|
38
|
-
* couldn't differentiate per-backend choices and produced the
|
|
39
|
-
* orphan-bug class (model from backend X persisting when switching
|
|
40
|
-
* to backend Y).
|
|
41
|
-
*
|
|
42
|
-
* Resolution order (see `core/models/active-model.ts`):
|
|
43
|
-
* 1. `modelByBackend[activeBackend]` if it validates on the catalog
|
|
44
|
-
* 2. `backend.getDefaultModel()` (canonical for backends that have one)
|
|
45
|
-
* 3. `config.backendDefaults[activeBackend]` (operator override)
|
|
46
|
-
* 4. `config.model` (only when activeBackend === config.backend)
|
|
47
|
-
* 5. null → "No model selected" UI + send guard refuses.
|
|
48
|
-
*/
|
|
49
|
-
modelByBackend?: Record<string, string>;
|
|
50
|
-
/**
|
|
51
|
-
* @deprecated Single-slot model field. Retained for back-compat with
|
|
52
|
-
* old stores; migrated into `modelByBackend` on load. New writes go
|
|
53
|
-
* through `setChatModelForBackend` instead.
|
|
54
|
-
*/
|
|
55
|
-
model?: string;
|
|
56
|
-
/**
|
|
57
|
-
* Backend override for this chat. When set, queries from this chat
|
|
58
|
-
* route to the override backend instead of the global `config.backend`.
|
|
59
|
-
* The backend controller refcounts pool instances, so two chats on
|
|
60
|
-
* two different backends keep both alive concurrently.
|
|
61
|
-
*
|
|
62
|
-
* Stored as the registry id (e.g. `"claude"`, `"openai-agents"`).
|
|
63
|
-
* Cleared via `setChatBackend(cid, undefined)` — chat reverts to
|
|
64
|
-
* the global default.
|
|
65
|
-
*/
|
|
66
|
-
backend?: string;
|
|
67
|
-
/** Effort level override (maps to SDK thinking + effort options). */
|
|
68
|
-
effort?: EffortLevel;
|
|
69
|
-
/** Whether pulse is enabled for this chat. */
|
|
70
|
-
pulse?: boolean;
|
|
71
|
-
/** Per-chat pulse check interval in milliseconds. */
|
|
72
|
-
pulseIntervalMs?: number;
|
|
73
|
-
/** Last message ID checked by pulse (persisted to avoid reprocessing on restart). */
|
|
74
|
-
pulseLastCheckMsgId?: number;
|
|
75
|
-
/**
|
|
76
|
-
* When true, the model picker filters to free-tier models by default.
|
|
77
|
-
* Only meaningful for backends that report free-tier metadata (currently
|
|
78
|
-
* `openai-agents` against OpenRouter); other backends ignore the flag.
|
|
79
|
-
*/
|
|
80
|
-
freeOnly?: boolean;
|
|
81
|
-
};
|
|
29
|
+
export type { ChatSettings } from "./repositories/chat-settings-repo.js";
|
|
30
|
+
import type { ChatSettings } from "./repositories/chat-settings-repo.js";
|
|
82
31
|
|
|
83
32
|
// In-memory cache over the chat_settings table. Reads serve live
|
|
84
33
|
// references from here; writes go through persist() so each mutation
|
|
@@ -488,9 +437,3 @@ export const EFFORT_LEVELS: EffortLevel[] = [
|
|
|
488
437
|
"max",
|
|
489
438
|
"xhigh",
|
|
490
439
|
];
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Resolve a user-provided model name (alias or full ID) to the canonical model ID.
|
|
494
|
-
* Delegates to the model registry; falls through unknown names unchanged.
|
|
495
|
-
*/
|
|
496
|
-
export { resolveModelId as resolveModelName } from "../core/models/catalog.js";
|
|
@@ -20,87 +20,13 @@ import { inTransaction } from "./db.js";
|
|
|
20
20
|
import * as repo from "./repositories/cron-repo.js";
|
|
21
21
|
import { nextDueMs, type CatchupPolicy } from "../native/scheduler-core.js";
|
|
22
22
|
|
|
23
|
-
export type
|
|
23
|
+
export type {
|
|
24
|
+
CronJob,
|
|
25
|
+
CronJobType,
|
|
26
|
+
CronRunStatus,
|
|
27
|
+
} from "./repositories/cron-repo.js";
|
|
24
28
|
export type { CatchupPolicy };
|
|
25
|
-
|
|
26
|
-
/** Outcome of the most recent execution — surfaced in list_cron_jobs. */
|
|
27
|
-
export type CronRunStatus = "ok" | "error";
|
|
28
|
-
|
|
29
|
-
export type CronJob = {
|
|
30
|
-
id: string;
|
|
31
|
-
chatId: string;
|
|
32
|
-
/**
|
|
33
|
-
* Cron expression (5-field: minute hour day month weekday). Optional — a job
|
|
34
|
-
* carries EITHER `schedule` (cron mode) OR `everyMs` (interval mode), never
|
|
35
|
-
* both. The store validator (`isCronJob`) enforces exactly one.
|
|
36
|
-
*/
|
|
37
|
-
schedule?: string;
|
|
38
|
-
/**
|
|
39
|
-
* Fixed interval in milliseconds (interval mode). Mutually exclusive with
|
|
40
|
-
* `schedule`. The job fires roughly every `everyMs` after its anchor
|
|
41
|
-
* (`lastRunAt`, else `startAt`, else `createdAt`). Wires the native
|
|
42
|
-
* scheduler-core interval math (next-due + missed-run catch-up) directly.
|
|
43
|
-
*/
|
|
44
|
-
everyMs?: number;
|
|
45
|
-
/** "message" sends content as text; "query" runs content as a Claude prompt with tools */
|
|
46
|
-
type: CronJobType;
|
|
47
|
-
/** The message text or query prompt */
|
|
48
|
-
content: string;
|
|
49
|
-
/** Human-readable name for the job */
|
|
50
|
-
name: string;
|
|
51
|
-
enabled: boolean;
|
|
52
|
-
createdAt: number;
|
|
53
|
-
lastRunAt?: number;
|
|
54
|
-
runCount: number;
|
|
55
|
-
/** IANA timezone (e.g. "America/New_York"). Defaults to system timezone. */
|
|
56
|
-
timezone?: string;
|
|
57
|
-
/**
|
|
58
|
-
* Optional model override for `query` jobs. Unset = the chat's model. `query`
|
|
59
|
-
* cron jobs run as an isolated one-shot (no chat session), so unlike triggers
|
|
60
|
-
* the model may be on a different provider — see `provider`.
|
|
61
|
-
*/
|
|
62
|
-
model?: string;
|
|
63
|
-
/**
|
|
64
|
-
* Optional provider/backend id for the override (e.g. a cheaper provider than
|
|
65
|
-
* the chat). Requires `model`. Unset = the chat's backend. Since cron runs
|
|
66
|
-
* isolated, a different provider is fine here.
|
|
67
|
-
*/
|
|
68
|
-
provider?: string;
|
|
69
|
-
/**
|
|
70
|
-
* Optional short brief that becomes the isolated agent's system prompt — what
|
|
71
|
-
* the job is and how to do it. Useful to orient a cheaper override model.
|
|
72
|
-
*/
|
|
73
|
-
instructions?: string;
|
|
74
|
-
/**
|
|
75
|
-
* Don't fire before this epoch-ms instant (a delayed start / "not before").
|
|
76
|
-
* Unset = eligible immediately.
|
|
77
|
-
*/
|
|
78
|
-
startAt?: number;
|
|
79
|
-
/**
|
|
80
|
-
* Don't fire after this epoch-ms instant; the job auto-disables once now
|
|
81
|
-
* passes it (a natural expiry / "until"). Unset = no end.
|
|
82
|
-
*/
|
|
83
|
-
endAt?: number;
|
|
84
|
-
/**
|
|
85
|
-
* Auto-disable after this many total runs (`runCount >= maxRuns`). A value of
|
|
86
|
-
* 1 makes the job one-shot. Unset = unbounded.
|
|
87
|
-
*/
|
|
88
|
-
maxRuns?: number;
|
|
89
|
-
/**
|
|
90
|
-
* Missed-run policy for runs that were due while Talon was down:
|
|
91
|
-
* "skip" (default) — drop them, resume on the next due tick
|
|
92
|
-
* "once" — collapse any number of missed runs into a single catch-up
|
|
93
|
-
* "all" — replay every missed run, capped by CATCHUP_MAX
|
|
94
|
-
* Decided by the native scheduler-core `catchupRunCount`.
|
|
95
|
-
*/
|
|
96
|
-
catchup?: CatchupPolicy;
|
|
97
|
-
/** Status of the most recent execution. */
|
|
98
|
-
lastStatus?: CronRunStatus;
|
|
99
|
-
/** Error message from the most recent failed execution (cleared on success). */
|
|
100
|
-
lastError?: string;
|
|
101
|
-
/** Wall-clock duration of the most recent execution, in ms. */
|
|
102
|
-
lastDurationMs?: number;
|
|
103
|
-
};
|
|
29
|
+
import type { CronJob, CronRunStatus } from "./repositories/cron-repo.js";
|
|
104
30
|
|
|
105
31
|
/** Telemetry recorded after each execution attempt. */
|
|
106
32
|
export type CronRunOutcome = {
|