talon-agent 3.0.5 → 3.1.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.
- package/package.json +8 -5
- 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/index.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/auth.ts +52 -0
- package/src/frontend/native/extensions.ts +2 -2
- package/src/frontend/native/index.ts +8 -2
- package/src/frontend/native/server.ts +96 -15
- package/src/frontend/telegram/callbacks/model.ts +1 -1
- package/src/frontend/telegram/commands/admin.ts +1 -1
- package/src/frontend/telegram/commands/settings.ts +1 -1
- package/src/frontend/telegram/formatting.ts +1 -1
- package/src/frontend/terminal/commands.ts +1 -1
- package/src/native/htmlents-wasm-bytes.ts +4 -4
- package/src/native/htmlents.ts +4 -4
- package/src/native/registry.ts +9 -9
- package/src/native/runtime.ts +2 -2
- package/src/native/sqlguard-wasm-bytes.ts +4 -4
- package/src/native/sqlguard.ts +2 -2
- package/src/native/strsim-wasm-bytes.ts +4 -4
- package/src/native/strsim.ts +3 -3
- 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 +3 -14
- 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/src/util/config.ts +9 -2
- package/src/util/harden.ts +50 -0
- package/src/util/log.ts +2 -1
- package/tsconfig.json +6 -0
package/src/storage/history.ts
CHANGED
|
@@ -26,19 +26,8 @@ import { formatSmartTimestamp, formatRelativeAge } from "../util/time.js";
|
|
|
26
26
|
import { importLegacyJson } from "./legacy-import.js";
|
|
27
27
|
import * as repo from "./repositories/history-repo.js";
|
|
28
28
|
|
|
29
|
-
export type HistoryMessage
|
|
30
|
-
|
|
31
|
-
senderId: number;
|
|
32
|
-
senderName: string;
|
|
33
|
-
text: string;
|
|
34
|
-
replyToMsgId?: number;
|
|
35
|
-
timestamp: number;
|
|
36
|
-
mediaType?:
|
|
37
|
-
"photo" | "document" | "voice" | "sticker" | "video" | "animation";
|
|
38
|
-
stickerFileId?: string;
|
|
39
|
-
/** Saved file path for downloaded media. */
|
|
40
|
-
filePath?: string;
|
|
41
|
-
};
|
|
29
|
+
export type { HistoryMessage } from "./repositories/history-repo.js";
|
|
30
|
+
import type { HistoryMessage } from "./repositories/history-repo.js";
|
|
42
31
|
|
|
43
32
|
// ── Persistence lifecycle ───────────────────────────────────────────────────
|
|
44
33
|
|
|
@@ -166,7 +155,7 @@ export function getRecentFormatted(chatId: string, limit = 20): string {
|
|
|
166
155
|
* Build an FTS5 MATCH expression from free-form user input. Every
|
|
167
156
|
* token is double-quoted so FTS operators (AND, NEAR, *, ^) in user
|
|
168
157
|
* text are treated as literals, not syntax. Delegates to the C core
|
|
169
|
-
* (native/sqlguard-
|
|
158
|
+
* (native/sqlguard-wasm) for byte-identical output.
|
|
170
159
|
*/
|
|
171
160
|
function ftsQuery(query: string): string {
|
|
172
161
|
return ftsQuote(query);
|
package/src/storage/journal.ts
CHANGED
|
@@ -15,10 +15,21 @@
|
|
|
15
15
|
* PRUNE_EVERY appends) rather than on a timer.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { PublishedEvent, TalonEvent } from "../core/bus/index.js";
|
|
19
18
|
import { logError } from "../util/log.js";
|
|
20
19
|
import * as repo from "./repositories/journal-repo.js";
|
|
21
20
|
|
|
21
|
+
/**
|
|
22
|
+
* The shape the journal needs from a record: a type column to index on
|
|
23
|
+
* and a publish time. The bus's `PublishedEvent` satisfies it; the
|
|
24
|
+
* journal itself stays ignorant of the event vocabulary (storage never
|
|
25
|
+
* imports upward from core) — readers narrow with the type parameter
|
|
26
|
+
* on `readJournal`.
|
|
27
|
+
*/
|
|
28
|
+
export interface JournalRecord {
|
|
29
|
+
readonly type: string;
|
|
30
|
+
readonly at: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
/** Rows kept after a prune — bounded, but generous for a busy daemon. */
|
|
23
34
|
export const JOURNAL_RETENTION = 20_000;
|
|
24
35
|
|
|
@@ -26,12 +37,12 @@ export const JOURNAL_RETENTION = 20_000;
|
|
|
26
37
|
const PRUNE_EVERY = 500;
|
|
27
38
|
|
|
28
39
|
/** One journal record: the event plus its durable cursor and time. */
|
|
29
|
-
export interface JournalEntry {
|
|
40
|
+
export interface JournalEntry<E extends JournalRecord = JournalRecord> {
|
|
30
41
|
/** Durable, monotonic cursor — survives restarts (unlike bus ids). */
|
|
31
42
|
readonly seq: number;
|
|
32
43
|
/** Publish time, epoch ms. */
|
|
33
44
|
readonly at: number;
|
|
34
|
-
readonly event:
|
|
45
|
+
readonly event: E;
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
let appendsSincePrune = 0;
|
|
@@ -42,7 +53,7 @@ let appendFailureLogged = false;
|
|
|
42
53
|
* observer, and a full disk or locked database must not break the bus
|
|
43
54
|
* or the publisher behind it.
|
|
44
55
|
*/
|
|
45
|
-
export function appendToJournal(event:
|
|
56
|
+
export function appendToJournal(event: JournalRecord): void {
|
|
46
57
|
try {
|
|
47
58
|
repo.append(event.at, event.type, JSON.stringify(event));
|
|
48
59
|
if (++appendsSincePrune >= PRUNE_EVERY) {
|
|
@@ -63,20 +74,20 @@ export function appendToJournal(event: PublishedEvent): void {
|
|
|
63
74
|
* (indexed). Rows whose payload no longer parses are skipped — a
|
|
64
75
|
* corrupt row must not take down the readable ones around it.
|
|
65
76
|
*/
|
|
66
|
-
export function readJournal(
|
|
67
|
-
options: { limit?: number; type?:
|
|
68
|
-
): JournalEntry[] {
|
|
77
|
+
export function readJournal<E extends JournalRecord = JournalRecord>(
|
|
78
|
+
options: { limit?: number; type?: E["type"] } = {},
|
|
79
|
+
): JournalEntry<E>[] {
|
|
69
80
|
const limit = options.limit ?? 100;
|
|
70
81
|
const rows = options.type
|
|
71
82
|
? repo.recentByType(options.type, limit)
|
|
72
83
|
: repo.recent(limit);
|
|
73
|
-
const entries: JournalEntry[] = [];
|
|
84
|
+
const entries: JournalEntry<E>[] = [];
|
|
74
85
|
for (const row of rows) {
|
|
75
86
|
try {
|
|
76
87
|
entries.push({
|
|
77
88
|
seq: row.seq,
|
|
78
89
|
at: row.at,
|
|
79
|
-
event: JSON.parse(row.payload) as
|
|
90
|
+
event: JSON.parse(row.payload) as E,
|
|
80
91
|
});
|
|
81
92
|
} catch {
|
|
82
93
|
// skip the corrupt row
|
|
@@ -24,28 +24,8 @@ import { importLegacyJson } from "./legacy-import.js";
|
|
|
24
24
|
import { setMessageFilePath } from "./history.js";
|
|
25
25
|
import * as repo from "./repositories/media-index-repo.js";
|
|
26
26
|
|
|
27
|
-
export type MediaEntry
|
|
28
|
-
|
|
29
|
-
chatId: string;
|
|
30
|
-
msgId: number;
|
|
31
|
-
senderName: string;
|
|
32
|
-
type:
|
|
33
|
-
| "photo"
|
|
34
|
-
| "document"
|
|
35
|
-
| "voice"
|
|
36
|
-
| "video"
|
|
37
|
-
| "animation"
|
|
38
|
-
| "audio"
|
|
39
|
-
| "sticker";
|
|
40
|
-
filePath: string;
|
|
41
|
-
caption?: string;
|
|
42
|
-
timestamp: number;
|
|
43
|
-
/**
|
|
44
|
-
* BLAKE3 hex digest of the file contents (native/blake3-wasm).
|
|
45
|
-
* Filled in asynchronously after addMedia; undefined until hashed.
|
|
46
|
-
*/
|
|
47
|
-
contentHash?: string;
|
|
48
|
-
};
|
|
27
|
+
export type { MediaEntry } from "./repositories/media-index-repo.js";
|
|
28
|
+
import type { MediaEntry } from "./repositories/media-index-repo.js";
|
|
49
29
|
|
|
50
30
|
const RETENTION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
51
31
|
|
|
@@ -13,7 +13,61 @@
|
|
|
13
13
|
|
|
14
14
|
import { getDatabase, inTransaction } from "../db.js";
|
|
15
15
|
import { chatSettingsSql } from "../sql/statements.generated.js";
|
|
16
|
-
import type {
|
|
16
|
+
import type { ReasoningEffortLevel } from "../../types/effort.js";
|
|
17
|
+
|
|
18
|
+
export type ChatSettings = {
|
|
19
|
+
/**
|
|
20
|
+
* Per-backend model overrides for this chat. Keyed by backend id
|
|
21
|
+
* (`"claude"`, `"codex"`, `"openai-agents"`, etc). Each entry is the
|
|
22
|
+
* model id the user picked on that backend.
|
|
23
|
+
*
|
|
24
|
+
* Switching backends preserves each side's last pick — your Codex
|
|
25
|
+
* chat remembers `gpt-5.5`, your OpenRouter chat remembers
|
|
26
|
+
* `meta-llama/...`. Replaces the single legacy `model` field which
|
|
27
|
+
* couldn't differentiate per-backend choices and produced the
|
|
28
|
+
* orphan-bug class (model from backend X persisting when switching
|
|
29
|
+
* to backend Y).
|
|
30
|
+
*
|
|
31
|
+
* Resolution order (see `core/models/active-model.ts`):
|
|
32
|
+
* 1. `modelByBackend[activeBackend]` if it validates on the catalog
|
|
33
|
+
* 2. `backend.getDefaultModel()` (canonical for backends that have one)
|
|
34
|
+
* 3. `config.backendDefaults[activeBackend]` (operator override)
|
|
35
|
+
* 4. `config.model` (only when activeBackend === config.backend)
|
|
36
|
+
* 5. null → "No model selected" UI + send guard refuses.
|
|
37
|
+
*/
|
|
38
|
+
modelByBackend?: Record<string, string>;
|
|
39
|
+
/**
|
|
40
|
+
* @deprecated Single-slot model field. Retained for back-compat with
|
|
41
|
+
* old stores; migrated into `modelByBackend` on load. New writes go
|
|
42
|
+
* through `setChatModelForBackend` instead.
|
|
43
|
+
*/
|
|
44
|
+
model?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Backend override for this chat. When set, queries from this chat
|
|
47
|
+
* route to the override backend instead of the global `config.backend`.
|
|
48
|
+
* The backend controller refcounts pool instances, so two chats on
|
|
49
|
+
* two different backends keep both alive concurrently.
|
|
50
|
+
*
|
|
51
|
+
* Stored as the registry id (e.g. `"claude"`, `"openai-agents"`).
|
|
52
|
+
* Cleared via `setChatBackend(cid, undefined)` — chat reverts to
|
|
53
|
+
* the global default.
|
|
54
|
+
*/
|
|
55
|
+
backend?: string;
|
|
56
|
+
/** Effort level override (maps to SDK thinking + effort options). */
|
|
57
|
+
effort?: ReasoningEffortLevel;
|
|
58
|
+
/** Whether pulse is enabled for this chat. */
|
|
59
|
+
pulse?: boolean;
|
|
60
|
+
/** Per-chat pulse check interval in milliseconds. */
|
|
61
|
+
pulseIntervalMs?: number;
|
|
62
|
+
/** Last message ID checked by pulse (persisted to avoid reprocessing on restart). */
|
|
63
|
+
pulseLastCheckMsgId?: number;
|
|
64
|
+
/**
|
|
65
|
+
* When true, the model picker filters to free-tier models by default.
|
|
66
|
+
* Only meaningful for backends that report free-tier metadata (currently
|
|
67
|
+
* `openai-agents` against OpenRouter); other backends ignore the flag.
|
|
68
|
+
*/
|
|
69
|
+
freeOnly?: boolean;
|
|
70
|
+
};
|
|
17
71
|
|
|
18
72
|
export function upsert(chatId: string, settings: ChatSettings): void {
|
|
19
73
|
getDatabase()
|
|
@@ -7,12 +7,88 @@
|
|
|
7
7
|
|
|
8
8
|
import { getDatabase } from "../db.js";
|
|
9
9
|
import { cronSql } from "../sql/statements.generated.js";
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
import type { CatchupPolicy } from "../../native/scheduler-core.js";
|
|
11
|
+
|
|
12
|
+
export type CronJobType = "message" | "query";
|
|
13
|
+
|
|
14
|
+
/** Outcome of the most recent execution — surfaced in list_cron_jobs. */
|
|
15
|
+
export type CronRunStatus = "ok" | "error";
|
|
16
|
+
|
|
17
|
+
export type CronJob = {
|
|
18
|
+
id: string;
|
|
19
|
+
chatId: string;
|
|
20
|
+
/**
|
|
21
|
+
* Cron expression (5-field: minute hour day month weekday). Optional — a job
|
|
22
|
+
* carries EITHER `schedule` (cron mode) OR `everyMs` (interval mode), never
|
|
23
|
+
* both. The store validator (`isCronJob`) enforces exactly one.
|
|
24
|
+
*/
|
|
25
|
+
schedule?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Fixed interval in milliseconds (interval mode). Mutually exclusive with
|
|
28
|
+
* `schedule`. The job fires roughly every `everyMs` after its anchor
|
|
29
|
+
* (`lastRunAt`, else `startAt`, else `createdAt`). Wires the native
|
|
30
|
+
* scheduler-core interval math (next-due + missed-run catch-up) directly.
|
|
31
|
+
*/
|
|
32
|
+
everyMs?: number;
|
|
33
|
+
/** "message" sends content as text; "query" runs content as a Claude prompt with tools */
|
|
34
|
+
type: CronJobType;
|
|
35
|
+
/** The message text or query prompt */
|
|
36
|
+
content: string;
|
|
37
|
+
/** Human-readable name for the job */
|
|
38
|
+
name: string;
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
createdAt: number;
|
|
41
|
+
lastRunAt?: number;
|
|
42
|
+
runCount: number;
|
|
43
|
+
/** IANA timezone (e.g. "America/New_York"). Defaults to system timezone. */
|
|
44
|
+
timezone?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional model override for `query` jobs. Unset = the chat's model. `query`
|
|
47
|
+
* cron jobs run as an isolated one-shot (no chat session), so unlike triggers
|
|
48
|
+
* the model may be on a different provider — see `provider`.
|
|
49
|
+
*/
|
|
50
|
+
model?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Optional provider/backend id for the override (e.g. a cheaper provider than
|
|
53
|
+
* the chat). Requires `model`. Unset = the chat's backend. Since cron runs
|
|
54
|
+
* isolated, a different provider is fine here.
|
|
55
|
+
*/
|
|
56
|
+
provider?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Optional short brief that becomes the isolated agent's system prompt — what
|
|
59
|
+
* the job is and how to do it. Useful to orient a cheaper override model.
|
|
60
|
+
*/
|
|
61
|
+
instructions?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Don't fire before this epoch-ms instant (a delayed start / "not before").
|
|
64
|
+
* Unset = eligible immediately.
|
|
65
|
+
*/
|
|
66
|
+
startAt?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Don't fire after this epoch-ms instant; the job auto-disables once now
|
|
69
|
+
* passes it (a natural expiry / "until"). Unset = no end.
|
|
70
|
+
*/
|
|
71
|
+
endAt?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Auto-disable after this many total runs (`runCount >= maxRuns`). A value of
|
|
74
|
+
* 1 makes the job one-shot. Unset = unbounded.
|
|
75
|
+
*/
|
|
76
|
+
maxRuns?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Missed-run policy for runs that were due while Talon was down:
|
|
79
|
+
* "skip" (default) — drop them, resume on the next due tick
|
|
80
|
+
* "once" — collapse any number of missed runs into a single catch-up
|
|
81
|
+
* "all" — replay every missed run, capped by CATCHUP_MAX
|
|
82
|
+
* Decided by the native scheduler-core `catchupRunCount`.
|
|
83
|
+
*/
|
|
84
|
+
catchup?: CatchupPolicy;
|
|
85
|
+
/** Status of the most recent execution. */
|
|
86
|
+
lastStatus?: CronRunStatus;
|
|
87
|
+
/** Error message from the most recent failed execution (cleared on success). */
|
|
88
|
+
lastError?: string;
|
|
89
|
+
/** Wall-clock duration of the most recent execution, in ms. */
|
|
90
|
+
lastDurationMs?: number;
|
|
91
|
+
};
|
|
16
92
|
|
|
17
93
|
type Row = {
|
|
18
94
|
id: string;
|
|
@@ -7,7 +7,26 @@
|
|
|
7
7
|
|
|
8
8
|
import { getDatabase } from "../db.js";
|
|
9
9
|
import { goalsSql } from "../sql/statements.generated.js";
|
|
10
|
-
|
|
10
|
+
export type GoalStatus = "active" | "paused" | "completed" | "abandoned";
|
|
11
|
+
export type GoalPriority = "low" | "normal" | "high";
|
|
12
|
+
|
|
13
|
+
/** One persistent goal as the domain sees it. */
|
|
14
|
+
export type Goal = {
|
|
15
|
+
id: string;
|
|
16
|
+
/** Chat the goal belongs to — progress reports route back here. */
|
|
17
|
+
chatId: string;
|
|
18
|
+
title: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
status: GoalStatus;
|
|
21
|
+
priority: GoalPriority;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
/** Optional soft deadline (unix ms). */
|
|
25
|
+
dueAt?: number;
|
|
26
|
+
/** Rolling note from the last progress update. */
|
|
27
|
+
lastProgressNote?: string;
|
|
28
|
+
lastProgressAt?: number;
|
|
29
|
+
};
|
|
11
30
|
|
|
12
31
|
type Row = {
|
|
13
32
|
id: string;
|
|
@@ -8,7 +8,20 @@
|
|
|
8
8
|
import { escapeLike } from "../../native/sqlguard.js";
|
|
9
9
|
import { getDatabase, inTransaction } from "../db.js";
|
|
10
10
|
import { historySql } from "../sql/statements.generated.js";
|
|
11
|
-
|
|
11
|
+
/** One chat message as the domain sees it — the shape the rows map to. */
|
|
12
|
+
export type HistoryMessage = {
|
|
13
|
+
msgId: number;
|
|
14
|
+
senderId: number;
|
|
15
|
+
senderName: string;
|
|
16
|
+
text: string;
|
|
17
|
+
replyToMsgId?: number;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
mediaType?:
|
|
20
|
+
"photo" | "document" | "voice" | "sticker" | "video" | "animation";
|
|
21
|
+
stickerFileId?: string;
|
|
22
|
+
/** Saved file path for downloaded media. */
|
|
23
|
+
filePath?: string;
|
|
24
|
+
};
|
|
12
25
|
|
|
13
26
|
type Row = {
|
|
14
27
|
msg_id: number;
|
|
@@ -14,7 +14,29 @@
|
|
|
14
14
|
|
|
15
15
|
import { getDatabase, inTransaction } from "../db.js";
|
|
16
16
|
import { mediaIndexSql } from "../sql/statements.generated.js";
|
|
17
|
-
|
|
17
|
+
/** One indexed media file as the domain sees it. */
|
|
18
|
+
export type MediaEntry = {
|
|
19
|
+
id: string; // unique key: chatId:msgId
|
|
20
|
+
chatId: string;
|
|
21
|
+
msgId: number;
|
|
22
|
+
senderName: string;
|
|
23
|
+
type:
|
|
24
|
+
| "photo"
|
|
25
|
+
| "document"
|
|
26
|
+
| "voice"
|
|
27
|
+
| "video"
|
|
28
|
+
| "animation"
|
|
29
|
+
| "audio"
|
|
30
|
+
| "sticker";
|
|
31
|
+
filePath: string;
|
|
32
|
+
caption?: string;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
/**
|
|
35
|
+
* BLAKE3 hex digest of the file contents (native/blake3-wasm).
|
|
36
|
+
* Filled in asynchronously after addMedia; undefined until hashed.
|
|
37
|
+
*/
|
|
38
|
+
contentHash?: string;
|
|
39
|
+
};
|
|
18
40
|
|
|
19
41
|
type Row = {
|
|
20
42
|
chat_id: string;
|
|
@@ -8,7 +8,22 @@
|
|
|
8
8
|
|
|
9
9
|
import { getDatabase } from "../db.js";
|
|
10
10
|
import { scriptsSql } from "../sql/statements.generated.js";
|
|
11
|
-
|
|
11
|
+
export type ScriptLanguage = "bash" | "python" | "node";
|
|
12
|
+
|
|
13
|
+
/** One saved script as the domain sees it. */
|
|
14
|
+
export type Script = {
|
|
15
|
+
id: string;
|
|
16
|
+
/** Unique lookup key — also the script's filename stem. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** One-liner shown in listings; tells the agent when to reach for it. */
|
|
19
|
+
description: string;
|
|
20
|
+
language: ScriptLanguage;
|
|
21
|
+
scriptPath: string;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
useCount: number;
|
|
25
|
+
lastUsedAt?: number;
|
|
26
|
+
};
|
|
12
27
|
|
|
13
28
|
type Row = {
|
|
14
29
|
id: string;
|
|
@@ -8,11 +8,66 @@
|
|
|
8
8
|
|
|
9
9
|
import { getDatabase } from "../db.js";
|
|
10
10
|
import { triggersSql } from "../sql/statements.generated.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
export type TriggerLanguage = "bash" | "python" | "node" | "lua";
|
|
12
|
+
|
|
13
|
+
export type TriggerStatus =
|
|
14
|
+
| "pending" // created, not yet spawned (transient)
|
|
15
|
+
| "running" // child process alive
|
|
16
|
+
| "fired" // exited 0 — fired final wake message
|
|
17
|
+
| "errored" // exited non-zero — fired error wake message
|
|
18
|
+
| "cancelled" // killed by user (trigger_cancel)
|
|
19
|
+
| "timed_out" // killed by hard timeout
|
|
20
|
+
| "terminated"; // killed by Talon shutdown / restart
|
|
21
|
+
|
|
22
|
+
export type Trigger = {
|
|
23
|
+
id: string;
|
|
24
|
+
chatId: string;
|
|
25
|
+
numericChatId: number;
|
|
26
|
+
name: string;
|
|
27
|
+
language: TriggerLanguage;
|
|
28
|
+
/** Absolute path to the script body on disk. */
|
|
29
|
+
scriptPath: string;
|
|
30
|
+
/** Absolute path to the run log (interleaved stdout+stderr). */
|
|
31
|
+
logPath: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
status: TriggerStatus;
|
|
34
|
+
createdAt: number;
|
|
35
|
+
startedAt?: number;
|
|
36
|
+
endedAt?: number;
|
|
37
|
+
/** PID of the child process while running (cleared on exit). */
|
|
38
|
+
pid?: number;
|
|
39
|
+
/** Linux /proc/<pid>/stat field 22 (start time in jiffies) captured at
|
|
40
|
+
* spawn. Used by killOrphan to defend against PID reuse — start time is
|
|
41
|
+
* monotonic per boot and unchanged by exec(), so a match guarantees the
|
|
42
|
+
* current owner of the PID is the same process we spawned. Undefined on
|
|
43
|
+
* non-Linux platforms. */
|
|
44
|
+
pidStarttime?: number;
|
|
45
|
+
/** Hard timeout in seconds. Default 24h, max 7d. */
|
|
46
|
+
timeoutSeconds: number;
|
|
47
|
+
/** Exit code on terminal status. */
|
|
48
|
+
exitCode?: number;
|
|
49
|
+
/** Total wake-ups fired for this trigger — sum of mid-run TALON_FIRE: lines
|
|
50
|
+
* plus the terminal exit fire. Incremented every time fireWake() runs. */
|
|
51
|
+
fireCount: number;
|
|
52
|
+
lastFireAt?: number;
|
|
53
|
+
/** Truncated tail of the most recent fire payload (for diagnostics). */
|
|
54
|
+
lastFirePayload?: string;
|
|
55
|
+
lastError?: string;
|
|
56
|
+
/** If true, the trigger is respawned on Talon startup if it was still
|
|
57
|
+
* active when Talon went down. Triggers in any terminal state
|
|
58
|
+
* (fired/errored/cancelled) are NOT respawned — only ones interrupted
|
|
59
|
+
* by Talon shutdown or crash. (Persistent triggers have no hard timeout,
|
|
60
|
+
* so timed_out is unreachable for them — see spawnTrigger.) */
|
|
61
|
+
persistent?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Optional model override for the wake-up turn — a model id valid on the
|
|
64
|
+
* chat's own backend. Unset = inherit the chat's model (preferred). When set,
|
|
65
|
+
* the fired wake-up runs on this (typically cheaper) model instead, while
|
|
66
|
+
* still resuming the chat session (restricted to the same backend so
|
|
67
|
+
* continuity is preserved).
|
|
68
|
+
*/
|
|
69
|
+
model?: string;
|
|
70
|
+
};
|
|
16
71
|
|
|
17
72
|
type Row = {
|
|
18
73
|
id: string;
|
|
@@ -22,21 +22,8 @@ import { dirname, resolve } from "node:path";
|
|
|
22
22
|
import { dirs } from "../util/paths.js";
|
|
23
23
|
import * as repo from "./repositories/scripts-repo.js";
|
|
24
24
|
|
|
25
|
-
export type
|
|
26
|
-
|
|
27
|
-
export type Script = {
|
|
28
|
-
id: string;
|
|
29
|
-
/** Unique lookup key — also the script's filename stem. */
|
|
30
|
-
name: string;
|
|
31
|
-
/** One-liner shown in listings; tells the agent when to reach for it. */
|
|
32
|
-
description: string;
|
|
33
|
-
language: ScriptLanguage;
|
|
34
|
-
scriptPath: string;
|
|
35
|
-
createdAt: number;
|
|
36
|
-
updatedAt: number;
|
|
37
|
-
useCount: number;
|
|
38
|
-
lastUsedAt?: number;
|
|
39
|
-
};
|
|
25
|
+
export type { Script, ScriptLanguage } from "./repositories/scripts-repo.js";
|
|
26
|
+
import type { Script, ScriptLanguage } from "./repositories/scripts-repo.js";
|
|
40
27
|
|
|
41
28
|
export const SCRIPT_LANGUAGES: readonly ScriptLanguage[] = [
|
|
42
29
|
"bash",
|