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.
Files changed (46) hide show
  1. package/package.json +6 -1
  2. package/src/backend/kilo/models/index.ts +4 -1
  3. package/src/backend/kilo/server.ts +17 -4
  4. package/src/backend/openai-agents/session.ts +1 -1
  5. package/src/backend/opencode/models/index.ts +4 -1
  6. package/src/backend/opencode/server.ts +17 -4
  7. package/src/backend/shared/system-prompt.ts +5 -0
  8. package/src/cli/events.ts +1 -1
  9. package/src/cli/tasks.ts +5 -1
  10. package/src/core/background/triggers/pid.ts +28 -0
  11. package/src/core/background/triggers/resume.ts +4 -24
  12. package/src/core/background/triggers/spawn.ts +1 -1
  13. package/src/core/engine/gateway-actions/plugins.ts +3 -3
  14. package/src/core/prompt/invalidation.ts +23 -0
  15. package/src/core/types.ts +2 -2
  16. package/src/frontend/discord/callbacks/components.ts +1 -1
  17. package/src/frontend/discord/commands/settings.ts +1 -1
  18. package/src/frontend/native/extensions.ts +2 -2
  19. package/src/frontend/telegram/callbacks/model.ts +1 -1
  20. package/src/frontend/telegram/commands/settings.ts +1 -1
  21. package/src/frontend/terminal/commands.ts +1 -1
  22. package/src/plugins/github/index.ts +1 -1
  23. package/src/plugins/mem0/index.ts +1 -1
  24. package/src/plugins/mempalace/index.ts +1 -1
  25. package/src/plugins/playwright/index.ts +1 -1
  26. package/src/storage/chat-settings.ts +3 -60
  27. package/src/storage/cron-store.ts +6 -80
  28. package/src/storage/goal-store.ts +10 -19
  29. package/src/storage/history.ts +2 -13
  30. package/src/storage/journal.ts +20 -9
  31. package/src/storage/media-index.ts +2 -22
  32. package/src/storage/repositories/chat-settings-repo.ts +55 -1
  33. package/src/storage/repositories/cron-repo.ts +82 -6
  34. package/src/storage/repositories/goals-repo.ts +20 -1
  35. package/src/storage/repositories/history-repo.ts +14 -1
  36. package/src/storage/repositories/media-index-repo.ts +23 -1
  37. package/src/storage/repositories/scripts-repo.ts +16 -1
  38. package/src/storage/repositories/sessions-repo.ts +1 -1
  39. package/src/storage/repositories/triggers-repo.ts +60 -5
  40. package/src/storage/script-store.ts +2 -15
  41. package/src/storage/session-record.ts +220 -0
  42. package/src/storage/sessions.ts +22 -210
  43. package/src/storage/trigger-store.ts +6 -60
  44. package/src/types/assets.d.ts +9 -0
  45. package/src/types/effort.ts +12 -0
  46. package/tsconfig.json +6 -0
@@ -17,25 +17,16 @@
17
17
  import { randomUUID } from "node:crypto";
18
18
  import * as repo from "./repositories/goals-repo.js";
19
19
 
20
- export type GoalStatus = "active" | "paused" | "completed" | "abandoned";
21
- export type GoalPriority = "low" | "normal" | "high";
22
-
23
- export type Goal = {
24
- id: string;
25
- /** Chat the goal belongs to — progress reports route back here. */
26
- chatId: string;
27
- title: string;
28
- description?: string;
29
- status: GoalStatus;
30
- priority: GoalPriority;
31
- createdAt: number;
32
- updatedAt: number;
33
- /** Optional soft deadline (unix ms). */
34
- dueAt?: number;
35
- /** Rolling note from the last progress update. */
36
- lastProgressNote?: string;
37
- lastProgressAt?: number;
38
- };
20
+ export type {
21
+ Goal,
22
+ GoalPriority,
23
+ GoalStatus,
24
+ } from "./repositories/goals-repo.js";
25
+ import type {
26
+ Goal,
27
+ GoalPriority,
28
+ GoalStatus,
29
+ } from "./repositories/goals-repo.js";
39
30
 
40
31
  export const GOAL_STATUSES: readonly GoalStatus[] = [
41
32
  "active",
@@ -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
- msgId: number;
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
 
@@ -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: TalonEvent;
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: PublishedEvent): void {
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?: TalonEvent["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 TalonEvent,
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
- id: string; // unique key: chatId:msgId
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 { ChatSettings } from "../chat-settings.js";
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
- CatchupPolicy,
12
- CronJob,
13
- CronJobType,
14
- CronRunStatus,
15
- } from "../cron-store.js";
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
- import type { Goal, GoalPriority, GoalStatus } from "../goal-store.js";
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
- import type { HistoryMessage } from "../history.js";
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
- import type { MediaEntry } from "../media-index.js";
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
- import type { Script, ScriptLanguage } from "../script-store.js";
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;
@@ -19,7 +19,7 @@ import {
19
19
  normaliseMetrics,
20
20
  type SessionMetrics,
21
21
  type SessionState,
22
- } from "../sessions.js";
22
+ } from "../session-record.js";
23
23
 
24
24
  type Row = {
25
25
  chat_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
- import type {
12
- Trigger,
13
- TriggerLanguage,
14
- TriggerStatus,
15
- } from "../trigger-store.js";
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 ScriptLanguage = "bash" | "python" | "node";
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",