@getfamiliar/shared 0.1.10 → 0.1.11

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.
@@ -73,8 +73,8 @@ export interface AgentRunRow {
73
73
  /** Optional prompt seed supplied by the queueing caller. */
74
74
  readonly prompt: string | null;
75
75
  /**
76
- * Resolved system prompt the agent ran under (SOUL.md +
77
- * CONTEXT.md + handler body + tool list). Populated by
76
+ * Resolved prompt the agent ran under: the rendered prompt
77
+ * template (system prompt, then the user-message head). Populated by
78
78
  * AgentRunner only when `core.logSystemPrompt` is enabled;
79
79
  * otherwise `null`. Read by the report layer's
80
80
  * `renderAgentrunStart` when `withDetails` is on.
@@ -56,9 +56,9 @@ export declare class HandlerCatalog {
56
56
  constructor(workspaceDir: string, writablePathGlobs?: readonly string[]);
57
57
  /**
58
58
  * Walk `workspace/` and return one {@link HandlerPath} per
59
- * candidate handler file. Excludes the global-context files at
60
- * the workspace root (`SOUL.md`, `CONTEXT.md`)
61
- * and the `skills/` subtree. Every other `.md` file under a topic
59
+ * candidate handler file. Excludes every file at the workspace root
60
+ * (`PROMPT.md`, `SOUL.md`, … — root files have no topic, so they are
61
+ * never handlers) and the `skills/` subtree. Every other `.md` file under a topic
62
62
  * directory is included, even if it's a knowledge file (e.g.
63
63
  * `people/anna.md`) — `HandlerCatalog` does not police what is or
64
64
  * isn't a "proper" handler; that's a handler-loading concern.
@@ -1,13 +1,6 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { matchesAnyGlob } from "./PathGlob.js";
4
- /**
5
- * Workspace files at the very top level that are not handlers and
6
- * must be filtered out of {@link HandlerCatalog.list}. These are the
7
- * documents read by `buildSystemPrompt` rather than executed as
8
- * handlers (see `CLAUDE.md` — markdown layers / global context).
9
- */
10
- const RESERVED_ROOT_FILES = new Set(["SOUL.md", "CONTEXT.md"]);
11
4
  /**
12
5
  * Top-level workspace folder holding shared recipes
13
6
  * (`skills/<name>/SKILL.md`). Skills are pure context loaded via
@@ -58,9 +51,9 @@ export class HandlerCatalog {
58
51
  }
59
52
  /**
60
53
  * Walk `workspace/` and return one {@link HandlerPath} per
61
- * candidate handler file. Excludes the global-context files at
62
- * the workspace root (`SOUL.md`, `CONTEXT.md`)
63
- * and the `skills/` subtree. Every other `.md` file under a topic
54
+ * candidate handler file. Excludes every file at the workspace root
55
+ * (`PROMPT.md`, `SOUL.md`, … — root files have no topic, so they are
56
+ * never handlers) and the `skills/` subtree. Every other `.md` file under a topic
64
57
  * directory is included, even if it's a knowledge file (e.g.
65
58
  * `people/anna.md`) — `HandlerCatalog` does not police what is or
66
59
  * isn't a "proper" handler; that's a handler-loading concern.
@@ -119,8 +112,8 @@ export class HandlerCatalog {
119
112
  /**
120
113
  * Recursively walk `dir`, accumulating one {@link HandlerPath} per
121
114
  * `.md` file. `segments` tracks the topic path from the workspace
122
- * root; an empty array means we're at the root, where reserved files
123
- * are skipped.
115
+ * root; an empty array means we're at the root, whose files are
116
+ * skipped (no topic, never a handler).
124
117
  */
125
118
  async function walkMarkdown(dir, segments, out, workspaceDir, writablePathGlobs) {
126
119
  let entries;
@@ -141,13 +134,10 @@ async function walkMarkdown(dir, segments, out, workspaceDir, writablePathGlobs)
141
134
  if (!entry.isFile() || !entry.name.endsWith(".md")) {
142
135
  continue;
143
136
  }
144
- if (segments.length === 0 && RESERVED_ROOT_FILES.has(entry.name)) {
145
- continue;
146
- }
147
137
  if (segments.length === 0) {
148
- // Stray `.md` at the workspace root with no topic folder —
149
- // not a handler. Skip silently rather than reporting an
150
- // empty topic.
138
+ // `.md` at the workspace root (PROMPT.md, SOUL.md, …) has no
139
+ // topic folder — never a handler. Skip silently rather than
140
+ // reporting an empty topic.
151
141
  continue;
152
142
  }
153
143
  const handlerBase = entry.name.slice(0, -".md".length);
package/build/Plugin.d.ts CHANGED
@@ -44,25 +44,44 @@ export type AnyCommandDef = CommandDef<any>;
44
44
  */
45
45
  /**
46
46
  * Async function a plugin registers via
47
- * `ctx.events.registerContextProvider(fn)` to contribute per-event
48
- * situational knowledge to the handler that is about to run. Receives
49
- * the agentrun about to execute and its parent event; returns the
50
- * markdown body injected into the current-run user message, right after
51
- * the `# Runtime` block (see `buildRuntimeContextBlock`). The system
52
- * prompt itself stays static so it remains a cacheable prefix.
53
- * Returning `null`, `undefined`, or an empty/whitespace string
47
+ * `ctx.prompt.registerPromptAppender(fn)` to append per-event
48
+ * situational knowledge to the prompt of the handler that is about to
49
+ * run. Receives the agentrun about to execute and its parent event;
50
+ * the returned markdown is appended to the current-run user message,
51
+ * after the part of the prompt template that follows `{CACHE_MARKER}`.
52
+ * The system prompt itself stays static so it remains a cacheable
53
+ * prefix. Returning `null`, `undefined`, or an empty/whitespace string
54
54
  * contributes nothing — useful for "only speak up when relevant"
55
- * providers.
55
+ * appenders.
56
56
  *
57
- * The function is called once per `buildRuntimeContextBlock` (i.e. once
58
- * per agentrun start). It runs in the host process — providers may close
59
- * over plugin-local services (DB handles, caches) without going through
60
- * the bastion. The PromptBuilder reaches the registry through the
61
- * bastion's `/event-context/` gateway, which fans out to every
62
- * registered provider in parallel; one bad provider must not poison
63
- * the prompt, so the gateway isolates rejections and per-call timeouts.
57
+ * Called once per agentrun start. It runs in the host process —
58
+ * appenders may close over plugin-local services (DB handles, caches)
59
+ * without going through the bastion. The PromptBuilder reaches the
60
+ * registry through the bastion's `/prompt-appenders/` gateway, which
61
+ * fans out to every registered appender in parallel; one bad appender
62
+ * must not poison the prompt, so the gateway isolates rejections and
63
+ * per-call timeouts.
64
64
  */
65
- export type EventContextProvider = (agentrun: AgentRunRow, event: EventRow) => Promise<string | null | undefined>;
65
+ export type PromptAppender = (agentrun: AgentRunRow, event: EventRow) => Promise<string | null | undefined>;
66
+ /**
67
+ * A plugin-provided prompt placeholder, registered via
68
+ * `ctx.prompt.registerPlaceholder`. Its value replaces `{name}` wherever
69
+ * a prompt template uses it. Plugin placeholders are always per-run
70
+ * (`dynamic`): they receive the agentrun and event, so templates should
71
+ * place them after `{CACHE_MARKER}`.
72
+ */
73
+ export interface PromptPlaceholderRegistration {
74
+ /** Upper-case name, e.g. `OPEN_TICKETS` (used as `{OPEN_TICKETS}`). */
75
+ readonly name: string;
76
+ /** One-line description shown by `familiar prompt placeholders`. */
77
+ readonly description: string;
78
+ /**
79
+ * Produce the value for one agentrun. `null` / `undefined` renders as
80
+ * an empty string. Values are inserted verbatim — placeholders inside
81
+ * them are never expanded.
82
+ */
83
+ readonly provide: (agentrun: AgentRunRow, event: EventRow) => Promise<string | null | undefined>;
84
+ }
66
85
  /**
67
86
  * Optional callbacks passed alongside `ctx.events.emit`. Bundled into
68
87
  * an options object so additional per-emit hooks (e.g. cancellation,
@@ -148,29 +167,37 @@ export interface HostContext {
148
167
  */
149
168
  readonly events: {
150
169
  emit(event: NewEvent, options?: EmitOptions): Promise<EmitHandle>;
170
+ };
171
+ /**
172
+ * Prompt-assembly hooks.
173
+ */
174
+ readonly prompt: {
151
175
  /**
152
176
  * Register an async function called once per agentrun start with
153
177
  * the agentrun row and its parent event. Its returned string is
154
- * injected below the `# Available tools` section in the system
155
- * prompt the handler runs against. Use this to teach the agent
156
- * situational facts a plugin knows — e.g. "the sender of this
157
- * mail is currently in a meeting", "this chat thread has 14
158
- * unread mails attached to it".
159
- *
160
- * Providers must be cheap to run: the PromptBuilder fans out to
161
- * every registered provider in parallel via the bastion's
162
- * `/event-context/` gateway and waits on them before assembling
163
- * the prompt. Slow providers delay the start of every agentrun.
178
+ * appended to the current-run user message (see
179
+ * {@link PromptAppender}). Use this to teach the agent situational
180
+ * facts a plugin knows — e.g. "the sender of this mail is
181
+ * currently in a meeting".
164
182
  *
165
- * A provider that returns `null`, `undefined`, or whitespace
166
- * contributes nothing — typical for "only speak up when
167
- * relevant" cases. Exceptions thrown inside the provider are
168
- * isolated by the gateway and logged with the plugin id; the
169
- * prompt still assembles with the remaining providers' output.
183
+ * Appenders must be cheap to run: the PromptBuilder fans out to
184
+ * every registered appender in parallel via the bastion's
185
+ * `/prompt-appenders/` gateway and waits on them before assembling
186
+ * the prompt. Slow appenders delay the start of every agentrun.
170
187
  *
171
188
  * Plugins typically call this from their `start(ctx)` hook.
172
189
  */
173
- registerContextProvider(fn: EventContextProvider): void;
190
+ registerPromptAppender(fn: PromptAppender): void;
191
+ /**
192
+ * Register a named placeholder that prompt templates can use as
193
+ * `{NAME}`. Call it from **`prepare()`** so the `familiar prompt`
194
+ * CLI lists it without the daemon.
195
+ *
196
+ * @throws {PromptPlaceholderConflictError} When the name is not
197
+ * `[A-Z][A-Z0-9_]*` or is already taken by the core or another
198
+ * plugin.
199
+ */
200
+ registerPlaceholder(registration: PromptPlaceholderRegistration): void;
174
201
  };
175
202
  /**
176
203
  * Chat capabilities.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Prompt placeholders — the `{NAME}` tokens a workspace prompt template
3
+ * (`PROMPT.md`, or a handler's `systemPrompt: <path.md>`) may contain.
4
+ *
5
+ * The container renders templates; the host owns plugin-registered
6
+ * placeholders and the `familiar prompt placeholders` CLI. Both sides
7
+ * need the core definitions (the container to know which names it
8
+ * resolves itself, the host to reject plugin registrations that collide
9
+ * with them and to list them), which is why they live here.
10
+ */
11
+ /** Workspace-root template used by handlers with `systemPrompt: default` (or none declared). */
12
+ export declare const PROMPT_TEMPLATE_FILE = "PROMPT.md";
13
+ /**
14
+ * Valid placeholder name: upper-case letter, then upper-case letters,
15
+ * digits or underscores. Workspace includes (`{path.md}`) are a
16
+ * separate token form and are not names.
17
+ */
18
+ export declare const PROMPT_PLACEHOLDER_NAME_PATTERN: RegExp;
19
+ /**
20
+ * Whether a placeholder's value is the same for every run of a handler
21
+ * (`static`, safe in the cacheable system-prompt prefix) or changes per
22
+ * run (`dynamic`, belongs after `{CACHE_MARKER}`).
23
+ */
24
+ export type PromptPlaceholderKind = "static" | "dynamic";
25
+ /** Name, purpose and cache behaviour of one placeholder. */
26
+ export interface PromptPlaceholderDefinition {
27
+ readonly name: string;
28
+ readonly description: string;
29
+ readonly kind: PromptPlaceholderKind;
30
+ }
31
+ /** Name of the marker splitting the template into system prompt and user-message head. */
32
+ export declare const CACHE_MARKER_PLACEHOLDER = "CACHE_MARKER";
33
+ /**
34
+ * Placeholders resolved by the container core. Rendered order in
35
+ * `familiar prompt placeholders` follows this list.
36
+ */
37
+ export declare const CORE_PROMPT_PLACEHOLDERS: readonly PromptPlaceholderDefinition[];
38
+ /**
39
+ * Thrown when a plugin registers a placeholder whose name is invalid or
40
+ * already taken by the core or another plugin.
41
+ */
42
+ export declare class PromptPlaceholderConflictError extends Error {
43
+ readonly placeholderName: string;
44
+ /**
45
+ * @param placeholderName The offending name.
46
+ * @param message Human-readable reason.
47
+ */
48
+ constructor(placeholderName: string, message: string);
49
+ }
50
+ /**
51
+ * Whether `name` is resolved by the container core.
52
+ *
53
+ * @param name Placeholder name without braces.
54
+ * @returns `true` for names in {@link CORE_PROMPT_PLACEHOLDERS}.
55
+ */
56
+ export declare function isCorePromptPlaceholder(name: string): boolean;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Prompt placeholders — the `{NAME}` tokens a workspace prompt template
3
+ * (`PROMPT.md`, or a handler's `systemPrompt: <path.md>`) may contain.
4
+ *
5
+ * The container renders templates; the host owns plugin-registered
6
+ * placeholders and the `familiar prompt placeholders` CLI. Both sides
7
+ * need the core definitions (the container to know which names it
8
+ * resolves itself, the host to reject plugin registrations that collide
9
+ * with them and to list them), which is why they live here.
10
+ */
11
+ /** Workspace-root template used by handlers with `systemPrompt: default` (or none declared). */
12
+ export const PROMPT_TEMPLATE_FILE = "PROMPT.md";
13
+ /**
14
+ * Valid placeholder name: upper-case letter, then upper-case letters,
15
+ * digits or underscores. Workspace includes (`{path.md}`) are a
16
+ * separate token form and are not names.
17
+ */
18
+ export const PROMPT_PLACEHOLDER_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/;
19
+ /** Name of the marker splitting the template into system prompt and user-message head. */
20
+ export const CACHE_MARKER_PLACEHOLDER = "CACHE_MARKER";
21
+ /**
22
+ * Placeholders resolved by the container core. Rendered order in
23
+ * `familiar prompt placeholders` follows this list.
24
+ */
25
+ export const CORE_PROMPT_PLACEHOLDERS = [
26
+ {
27
+ name: CACHE_MARKER_PLACEHOLDER,
28
+ description: "Splits the template: everything before it is the (cacheable) system prompt, everything after it leads the current user message.",
29
+ kind: "static",
30
+ },
31
+ {
32
+ name: "HANDLER_CONTENT",
33
+ description: "Body of the resolved handler file (merged with its parents). Placeholders inside it are expanded too.",
34
+ kind: "static",
35
+ },
36
+ {
37
+ name: "PRELOADED_SKILLS",
38
+ description: "Full content of the skills the handler lists in its `skills` frontmatter, one `# Skill` section each, or empty. Placeholders inside are expanded too.",
39
+ kind: "static",
40
+ },
41
+ {
42
+ name: "HANDLER_PATH",
43
+ description: "Workspace-relative path of the handler file, e.g. `chat/telegram/index.md`.",
44
+ kind: "static",
45
+ },
46
+ {
47
+ name: "HANDLER_INHERITS",
48
+ description: "Parent handler files this handler is merged with, as `a ← b`, or `(none)`.",
49
+ kind: "static",
50
+ },
51
+ {
52
+ name: "SKILL_LIST",
53
+ description: "Bullet list of the skills in `skills/` (id and description).",
54
+ kind: "static",
55
+ },
56
+ {
57
+ name: "TOOL_LIST",
58
+ description: "Bullet list of the tools preloaded for this run.",
59
+ kind: "static",
60
+ },
61
+ {
62
+ name: "PYTHON_PACKAGE_LIST",
63
+ description: "Comma-separated python packages installed in the agent container.",
64
+ kind: "static",
65
+ },
66
+ {
67
+ name: "WRITABLE_PATH_LIST",
68
+ description: "Comma-separated `core.writablePaths` globs a non-privileged run may write to.",
69
+ kind: "static",
70
+ },
71
+ {
72
+ name: "RUNTIME_LIST",
73
+ description: "Bullet list of run facts: current time, event topic, handler file, inheritance, outputChat, privileged.",
74
+ kind: "dynamic",
75
+ },
76
+ {
77
+ name: "CURRENT_TIME",
78
+ description: "Current wall-clock time with weekday and timezone.",
79
+ kind: "dynamic",
80
+ },
81
+ {
82
+ name: "EVENT_TOPIC",
83
+ description: "Topic of the event this run processes, e.g. `chat:telegram`.",
84
+ kind: "dynamic",
85
+ },
86
+ {
87
+ name: "PRIVILEGED",
88
+ description: "`yes` when the run stems from the system owner, otherwise `no`.",
89
+ kind: "dynamic",
90
+ },
91
+ {
92
+ name: "STAGED_FILE_LIST",
93
+ description: "Bullet list of files staged in this event's `/scratch/<event-id>/` folder.",
94
+ kind: "dynamic",
95
+ },
96
+ ];
97
+ /**
98
+ * Thrown when a plugin registers a placeholder whose name is invalid or
99
+ * already taken by the core or another plugin.
100
+ */
101
+ export class PromptPlaceholderConflictError extends Error {
102
+ placeholderName;
103
+ /**
104
+ * @param placeholderName The offending name.
105
+ * @param message Human-readable reason.
106
+ */
107
+ constructor(placeholderName, message) {
108
+ super(message);
109
+ this.name = "PromptPlaceholderConflictError";
110
+ this.placeholderName = placeholderName;
111
+ }
112
+ }
113
+ /**
114
+ * Whether `name` is resolved by the container core.
115
+ *
116
+ * @param name Placeholder name without braces.
117
+ * @returns `true` for names in {@link CORE_PROMPT_PLACEHOLDERS}.
118
+ */
119
+ export function isCorePromptPlaceholder(name) {
120
+ return CORE_PROMPT_PLACEHOLDERS.some((definition) => definition.name === name);
121
+ }
package/build/Schema.d.ts CHANGED
@@ -60,7 +60,7 @@ export declare const SCHEDULED_SUBAGENTS_CHANNEL = "scheduled_subagents_changed"
60
60
  * deepest-first and merging every existing layer (see HandlerFile.load).
61
61
  */
62
62
  export declare const TOPIC_PATTERN = "^\\w+(:\\w+)*$";
63
- export declare const SCHEMA_SQL = "\n-- Drop legacy single-table artifacts from before the events/agentruns\n-- split. Safe no-ops on fresh databases. The current schema reuses the\n-- name events_notify_new() for its own NOTIFY function, so we do NOT\n-- drop it here (CREATE OR REPLACE FUNCTION below handles the update);\n-- dropping would fail anyway once the current events_new_trg exists.\nDROP TRIGGER IF EXISTS events_notify_changed_trg ON events;\nDROP TRIGGER IF EXISTS events_notify_new_trg ON events;\nDROP FUNCTION IF EXISTS events_notify_changed();\nDROP INDEX IF EXISTS events_pending_priority_idx;\nDROP INDEX IF EXISTS events_state_priority_idx;\n\nCREATE TABLE IF NOT EXISTS events (\n id bigserial PRIMARY KEY,\n topic text NOT NULL,\n priority smallint NOT NULL DEFAULT 50,\n state text NOT NULL DEFAULT 'pending',\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n idempotency_key text UNIQUE,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT events_topic_format CHECK (topic ~ '^\\w+(:\\w+)*$')\n);\n\n-- Drop legacy columns from earlier schema generations. Single-instance\n-- project; column drops are fine.\nALTER TABLE events DROP COLUMN IF EXISTS supervisor_prompt;\nALTER TABLE events DROP COLUMN IF EXISTS causation_chain;\n\n-- Chat-aware event metadata. `is_chat` opts into automatic user-message\n-- persistence in EventBus.add; `preferred_chat_channel_id` names the\n-- channel an assistant reply should reach the user on. The container is\n-- channel-blind \u2014 both fields are read host-side (for routing) or via\n-- JOIN inside the chatmessages NOTIFY trigger.\nALTER TABLE events ADD COLUMN IF NOT EXISTS is_chat boolean NOT NULL DEFAULT false;\nALTER TABLE events ADD COLUMN IF NOT EXISTS preferred_chat_channel_id text;\n-- Required for non-chat events (validated at `EventBus.add` time and at\n-- the type level via `NewEvent`'s discriminated union); always null for\n-- chat events because the user's text in `chatmessages` already serves\n-- as the agent-visible \"what happened\" message. The EventWatcher copies\n-- this verbatim into the root `agentruns.prompt` so the agent sees the\n-- emitter's framing without the EventWatcher having to reach into the\n-- payload schema.\nALTER TABLE events ADD COLUMN IF NOT EXISTS prompt text;\n-- Optional override for the root agentrun's handler. When NULL the\n-- input-event watcher falls back to 'index' \u2014 same behaviour as\n-- before this column existed. Set by the emitter on NewEvent.\nALTER TABLE events ADD COLUMN IF NOT EXISTS start_handler text;\n-- Privileged events originate from a trusted user-input source (operator\n-- at the local terminal via cli-chat, operator on Telegram). The flag\n-- propagates verbatim to the root agentrun and to every child agentrun\n-- spawned via schedule_subagent / start_subagent, so future system tools can\n-- gate risky reads / writes (editing SOUL.md etc.) on whether the run\n-- descends from a trusted input. Default false: any plugin that doesn't\n-- explicitly opt in produces non-privileged events.\nALTER TABLE events ADD COLUMN IF NOT EXISTS privileged boolean NOT NULL DEFAULT false;\n-- When true and the event ends in 'failed', `HostContextImpl.emitAndAwait`\n-- writes a role='assistant' chatmessage with the failure text before\n-- rejecting handle.settled. Default false: only chat plugins (cli-chat,\n-- telegram) opt in. See [[outputChatOnFailure]] on NewEvent for the\n-- rationale.\nALTER TABLE events ADD COLUMN IF NOT EXISTS output_chat_on_failure boolean NOT NULL DEFAULT false;\n\nCREATE INDEX IF NOT EXISTS events_state_priority_idx\n ON events (state, priority DESC, id ASC);\nCREATE INDEX IF NOT EXISTS events_topic_idx ON events (topic);\n\nCREATE TABLE IF NOT EXISTS agentruns (\n id bigserial PRIMARY KEY,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n parent_agentrun_id bigint REFERENCES agentruns(id) ON DELETE CASCADE,\n topic text NOT NULL,\n handler text NOT NULL,\n priority smallint NOT NULL DEFAULT 50,\n state text NOT NULL DEFAULT 'pending',\n prompt text,\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n result jsonb,\n result_text text,\n error text,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT agentruns_topic_format CHECK (topic ~ '^\\w+(:\\w+)*$')\n);\n\n-- Forward-compat ALTER for databases created before result_text landed.\n-- Safe no-op on fresh databases where the column already exists.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS result_text text;\n-- Inherited from the originating event (root agentrun) or parent\n-- agentrun (children spawned via schedule_subagent / start_subagent). See\n-- the matching ALTER on the events table for the trust-model rationale.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS privileged boolean NOT NULL DEFAULT false;\n-- Number of retry attempts already made on this agentrun. Bumped each\n-- time AgentRunner postpones the row due to a retryable inference\n-- error (APICallError.isRetryable). Capped per-handler via maxRetries\n-- (frontmatter override) or globally via inference.maxRetries.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS retry_count int NOT NULL DEFAULT 0;\n-- Earliest moment the agentrun watcher is allowed to re-claim this\n-- row. NULL = claim immediately (default for fresh rows). A future\n-- timestamp = leave the row alone until then; the watcher's claim\n-- query gates on this so a parked row doesn't waste the slot while\n-- other agentruns (potentially using different models) can still run.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS not_before timestamptz;\n-- Resolved model id for this agentrun, of the form\n-- `<provider>/<modelId>` (e.g. `featherless/zai-org/GLM-5.1`).\n-- Populated by AgentRunner once it builds the model client; stays\n-- NULL for any agentrun that never reached generate() (failed at\n-- handler-load time, etc.).\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS model text;\n-- Resolved system prompt (SOUL.md + CONTEXT.md + handler body +\n-- tool list) used for this agentrun. Populated by AgentRunner\n-- only when core.logSystemPrompt is true; otherwise NULL. Surfaced\n-- by the report layer's Agentrun Start section when the consumer\n-- opts into withDetails.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS system_prompt text;\n-- Verbatim model-message array assembled for the first agent.generate()\n-- call (prior history + the run's leading user turn). Populated by\n-- AgentRunner only when inference.captureInitialMessageHistory is true;\n-- otherwise NULL. Surfaced by the report layer's -vv view so an operator\n-- can see exactly what was sent into the model loop.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS initial_messages jsonb;\n\n-- Distinguishes how a child agentrun was spawned.\n-- NULL \u2014 root agentrun (created by the input-event watcher, no parent).\n-- 'scheduled' \u2014 fire-and-forget child spawned by `schedule_subagent` without\n-- a future `when` (immediate dispatch under the same event).\n-- 'started' \u2014 synchronous child spawned via the `start_subagent` tool; the\n-- parent is parked in state='waiting' until this row settles.\n-- The Scheduler reads this column to decide whether settling a row should\n-- wake a waiting parent.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS calltype text;\nALTER TABLE agentruns DROP CONSTRAINT IF EXISTS agentruns_calltype_format;\nALTER TABLE agentruns ADD CONSTRAINT agentruns_calltype_format\n CHECK (calltype IS NULL OR calltype IN ('scheduled','started'));\n\n-- Partial index for the \"are all started children of $parent settled?\"\n-- check the Scheduler runs after every 'started' child settles. Indexes\n-- only the rows that actually participate in the question.\nCREATE INDEX IF NOT EXISTS agentruns_parent_calltype_state_idx\n ON agentruns (parent_agentrun_id, state)\n WHERE calltype = 'started';\n\n-- Forward-compat: relax the topic CHECK constraint to allow arbitrarily\n-- deep `a:b:c:\u2026` topics. CREATE TABLE IF NOT EXISTS above doesn't\n-- update existing constraints, so we drop+re-add unconditionally. The\n-- DROP IF EXISTS / ADD pair is idempotent.\nALTER TABLE events DROP CONSTRAINT IF EXISTS events_topic_format;\nALTER TABLE events ADD CONSTRAINT events_topic_format CHECK (topic ~ '^\\w+(:\\w+)*$');\nALTER TABLE agentruns DROP CONSTRAINT IF EXISTS agentruns_topic_format;\nALTER TABLE agentruns ADD CONSTRAINT agentruns_topic_format CHECK (topic ~ '^\\w+(:\\w+)*$');\n\nCREATE INDEX IF NOT EXISTS agentruns_state_priority_idx\n ON agentruns (state, priority DESC, id ASC);\nCREATE INDEX IF NOT EXISTS agentruns_event_id_idx ON agentruns (event_id);\nCREATE INDEX IF NOT EXISTS agentruns_parent_idx ON agentruns (parent_agentrun_id);\n\nCREATE TABLE IF NOT EXISTS stepresults (\n id bigserial PRIMARY KEY,\n agent_run_id bigint NOT NULL REFERENCES agentruns(id) ON DELETE CASCADE,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n step_number int NOT NULL,\n finish_reason text NOT NULL,\n result_text text,\n reasoning_text text,\n input_tokens int,\n output_tokens int,\n total_tokens int,\n tool_call_count int NOT NULL DEFAULT 0,\n tool_calls jsonb NOT NULL DEFAULT '[]'::jsonb,\n tool_results jsonb NOT NULL DEFAULT '[]'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT stepresults_step_number_unique UNIQUE (agent_run_id, step_number)\n);\n\nCREATE INDEX IF NOT EXISTS stepresults_agent_run_id_idx ON stepresults (agent_run_id);\nCREATE INDEX IF NOT EXISTS stepresults_event_id_idx ON stepresults (event_id);\n\n-- AI SDK per-step usage breakdown\n-- (LanguageModelUsage.inputTokenDetails / outputTokenDetails). Each\n-- column is NULL when the provider doesn't report that detail.\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_no_cache int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_cache_read int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_cache_write int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS output_tokens_text int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS output_tokens_reasoning int;\n-- Full step object snapshot, populated only when the operator opts\n-- in via inference.captureRawStepResultToDatabase: true. Used to\n-- inspect provider-specific fields (Anthropic cache control,\n-- OpenAI logprobs, ...) that don't fit dedicated columns.\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS raw_result jsonb;\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 chatmessages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Persistent chat history across all channels. Each row links to the\n-- event whose lifecycle the message belongs to:\n-- * For role='user': the event the host plugin emitted with isChat=true.\n-- * For role='assistant': the event whose agentrun called send_chat.\n-- The channel is reachable via JOIN to events.preferred_chat_channel_id;\n-- there is intentionally no channel_id column here \u2014 channel routing is\n-- a host-side concern, the container never names a channel.\n--\n-- delivered_at is set when ANY listener returns true from its async\n-- handler. New subscribers replay all undelivered matching rows on\n-- registration so a message produced when nobody was listening is still\n-- delivered later.\nCREATE TABLE IF NOT EXISTS chatmessages (\n id bigserial PRIMARY KEY,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n role text NOT NULL CHECK (role IN ('user','assistant')),\n text_content text NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n delivered_at timestamptz\n);\n\nCREATE INDEX IF NOT EXISTS chatmessages_event_idx\n ON chatmessages (event_id);\nCREATE INDEX IF NOT EXISTS chatmessages_undelivered_idx\n ON chatmessages (created_at) WHERE delivered_at IS NULL;\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 NOTIFY triggers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCREATE OR REPLACE FUNCTION events_notify_new() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify('events_new', NEW.id::text);\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS events_new_trg ON events;\nCREATE TRIGGER events_new_trg\n AFTER INSERT ON events\n FOR EACH ROW EXECUTE FUNCTION events_notify_new();\n\nCREATE OR REPLACE FUNCTION events_notify_state() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify('events_state', NEW.id::text || ':' || NEW.state);\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS events_state_trg ON events;\nCREATE TRIGGER events_state_trg\n AFTER UPDATE OF state ON events\n FOR EACH ROW EXECUTE FUNCTION events_notify_state();\n\nCREATE OR REPLACE FUNCTION agentruns_notify_changed() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify(\n 'agentruns_changed',\n NEW.event_id::text || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS agentruns_notify_changed_trg ON agentruns;\nCREATE TRIGGER agentruns_notify_changed_trg\n AFTER INSERT OR UPDATE OF state ON agentruns\n FOR EACH ROW EXECUTE FUNCTION agentruns_notify_changed();\n\nCREATE OR REPLACE FUNCTION stepresults_notify_new() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify(\n 'stepresults_new',\n NEW.event_id::text || ':' || NEW.agent_run_id::text || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS stepresults_new_trg ON stepresults;\nCREATE TRIGGER stepresults_new_trg\n AFTER INSERT ON stepresults\n FOR EACH ROW EXECUTE FUNCTION stepresults_notify_new();\n\n-- chatmessages NOTIFY: payload is \"<channel_id>:<role>:<id>\". Channel\n-- comes from the parent event's preferred_chat_channel_id, JOINed at\n-- trigger time. Empty channel becomes empty prefix \u2014 listeners will\n-- filter it out unless they explicitly subscribe to channelId=\"\".\nCREATE OR REPLACE FUNCTION chatmessages_notify_new() RETURNS trigger AS $$\nDECLARE\n channel text;\nBEGIN\n SELECT preferred_chat_channel_id INTO channel FROM events WHERE id = NEW.event_id;\n PERFORM pg_notify(\n 'chatmessages_new',\n COALESCE(channel, '') || ':' || NEW.role || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS chatmessages_new_trg ON chatmessages;\nCREATE TRIGGER chatmessages_new_trg\n AFTER INSERT ON chatmessages\n FOR EACH ROW EXECUTE FUNCTION chatmessages_notify_new();\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 calendars + calendar_events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Plugin-agnostic calendar data layer. `calendars` lists every\n-- subscription (own or shared) any provider plugin (ms365, future\n-- google/caldav) has surfaced; `calendar_events` stores every\n-- instance. Providers persist every occurrence of a recurring\n-- series independently (no master-expansion in core).\n--\n-- Identity:\n-- * `calendars.unique_key` is the provider's stable id. Uniqueness\n-- within `(plugin_id, unique_key)` lets multiple providers\n-- coexist.\n-- * `calendar_events.id` is provider-prefixed (e.g. `ms365:<graph-id>`)\n-- so collisions across providers are impossible by construction.\n--\n-- Refresh reconciliation uses a scan-generation tag: a refresh walk\n-- bumps `calendars.scan_generation`, upserts every event in window\n-- with the new value, then DELETEs events whose generation is older\n-- than the calendar's current value.\n\nCREATE TABLE IF NOT EXISTS calendars (\n id bigserial PRIMARY KEY,\n plugin_id text NOT NULL,\n unique_key text NOT NULL,\n name text NOT NULL,\n type text NOT NULL CHECK (type IN ('own','shared')),\n owner_name text,\n scan_generation int NOT NULL DEFAULT 0,\n created_at timestamptz NOT NULL DEFAULT now(),\n UNIQUE (plugin_id, unique_key)\n);\n\n-- The provider's own \"default calendar\" flag (Graph's\n-- `isDefaultCalendar`). Used by `CalendarService.resolveDefaultCalendar`\n-- as the fallback when `core.defaultCalendar` is unset \u2014 we pick the\n-- first registered calendar (lowest id) flagged here. Added via\n-- ALTER so existing databases don't need a schema reset.\nALTER TABLE calendars ADD COLUMN IF NOT EXISTS is_default smallint NOT NULL DEFAULT 0;\n\nCREATE INDEX IF NOT EXISTS calendars_plugin_id_idx ON calendars (plugin_id);\n\nCREATE TABLE IF NOT EXISTS calendar_events (\n id text PRIMARY KEY,\n calendar_id bigint NOT NULL REFERENCES calendars(id) ON DELETE CASCADE,\n series_master_id text,\n type text NOT NULL CHECK (type IN ('singleInstance','occurrence','exception','seriesMaster')),\n subject text,\n start_dt text NOT NULL,\n end_dt text NOT NULL,\n event_tz text,\n is_all_day smallint NOT NULL DEFAULT 0,\n is_cancelled smallint NOT NULL DEFAULT 0,\n show_as text,\n sensitivity text,\n importance text,\n location text,\n is_online_meeting smallint NOT NULL DEFAULT 0,\n online_meeting_url text,\n organizer_name text,\n organizer_email text,\n response_status text,\n attendees_json jsonb,\n body text,\n attachments jsonb,\n scan_generation int NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS calendar_events_window_idx\n ON calendar_events (calendar_id, start_dt);\nCREATE INDEX IF NOT EXISTS calendar_events_series_idx\n ON calendar_events (series_master_id);\nCREATE INDEX IF NOT EXISTS calendar_events_generation_idx\n ON calendar_events (calendar_id, scan_generation);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 scheduled_subagents \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- One-off agent-scheduled wake-ups. Each row is upserted by the\n-- container's `schedule_subagent` tool and read by the host-side\n-- `ScheduledSubagentScheduler`, which installs a Croner job per row\n-- and atomically deletes the row when it fires. `key` is the\n-- agent-supplied unique id used for replace-on-conflict semantics\n-- (\"rescheduling a key overwrites the previous timing\"). `fire_at`\n-- is the UTC firing time; the agent passes wall-clock-in-`core.timezone`\n-- and the container projects to UTC before insert. On daemon startup,\n-- past-due rows are deleted with a warn log; future rows are\n-- reinstalled. See [[feedback_agent_facing_format]] \u2014 UTC storage,\n-- local-tz projection at every agent-facing edge.\nCREATE TABLE IF NOT EXISTS scheduled_subagents (\n key text PRIMARY KEY,\n fire_at timestamptz NOT NULL,\n topic text NOT NULL,\n handler text NOT NULL,\n prompt text,\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n priority smallint NOT NULL DEFAULT 50,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT scheduled_subagents_topic_format CHECK (topic ~ '^\\w+(:\\w+)*$')\n);\n\nCREATE INDEX IF NOT EXISTS scheduled_subagents_fire_at_idx\n ON scheduled_subagents (fire_at);\n\nCREATE OR REPLACE FUNCTION scheduled_subagents_notify_changed() RETURNS trigger AS $$\nDECLARE\n payload_key text;\n op text;\nBEGIN\n IF TG_OP = 'DELETE' THEN\n payload_key := OLD.key;\n op := 'd';\n ELSE\n payload_key := NEW.key;\n op := 'u';\n END IF;\n PERFORM pg_notify('scheduled_subagents_changed', payload_key || ':' || op);\n RETURN COALESCE(NEW, OLD);\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS scheduled_subagents_notify_changed_trg ON scheduled_subagents;\nCREATE TRIGGER scheduled_subagents_notify_changed_trg\n AFTER INSERT OR UPDATE OR DELETE ON scheduled_subagents\n FOR EACH ROW EXECUTE FUNCTION scheduled_subagents_notify_changed();\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 inference_events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Append-only audit trail of every upstream model HTTP call attempt.\n-- Written by AgentRunner on each generate() outcome (success / retryable /\n-- fatal). Read by the host-side `inference_status` reflection tool to\n-- answer \"is this model misbehaving right now?\". One row per attempt;\n-- retries write multiple rows. NULL agent_run_id is kept for runs whose\n-- row was later deleted (parent event cascade) so the historical signal\n-- survives.\nCREATE TABLE IF NOT EXISTS inference_events (\n id bigserial PRIMARY KEY,\n provider text NOT NULL,\n model text NOT NULL,\n agent_run_id bigint REFERENCES agentruns(id) ON DELETE SET NULL,\n outcome text NOT NULL CHECK (outcome IN ('success','retryable','fatal')),\n status_code int,\n error_excerpt text,\n occurred_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS inference_events_model_time_idx\n ON inference_events (model, occurred_at DESC);\nCREATE INDEX IF NOT EXISTS inference_events_occurred_at_idx\n ON inference_events (occurred_at DESC);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 tool_calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Append-only record of every tool invocation an agentrun attempted,\n-- tagged with the resolved handler markdown path and whether the call\n-- threw. Written by the tool wrapper in the container's ToolsFactory on\n-- each execute() outcome. Read by the heuristic tool-preloader\n-- (ToolCallBus.topToolsForHandler): the tools a handler successfully\n-- used across its recent non-error runs get loaded up front on the next\n-- run, so warm handlers rarely need a tool_list / tool_call round-trip.\n-- NULL agent_run_id survives a parent-event cascade so the historical\n-- usage signal outlives the run row.\nCREATE TABLE IF NOT EXISTS tool_calls (\n id bigserial PRIMARY KEY,\n agent_run_id bigint REFERENCES agentruns(id) ON DELETE SET NULL,\n handler_path text NOT NULL,\n tool_name text NOT NULL,\n successful boolean NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS tool_calls_handler_run_idx\n ON tool_calls (handler_path, agent_run_id DESC);\nCREATE INDEX IF NOT EXISTS tool_calls_created_at_idx\n ON tool_calls (created_at DESC);\n";
63
+ export declare const SCHEMA_SQL = "\n-- Drop legacy single-table artifacts from before the events/agentruns\n-- split. Safe no-ops on fresh databases. The current schema reuses the\n-- name events_notify_new() for its own NOTIFY function, so we do NOT\n-- drop it here (CREATE OR REPLACE FUNCTION below handles the update);\n-- dropping would fail anyway once the current events_new_trg exists.\nDROP TRIGGER IF EXISTS events_notify_changed_trg ON events;\nDROP TRIGGER IF EXISTS events_notify_new_trg ON events;\nDROP FUNCTION IF EXISTS events_notify_changed();\nDROP INDEX IF EXISTS events_pending_priority_idx;\nDROP INDEX IF EXISTS events_state_priority_idx;\n\nCREATE TABLE IF NOT EXISTS events (\n id bigserial PRIMARY KEY,\n topic text NOT NULL,\n priority smallint NOT NULL DEFAULT 50,\n state text NOT NULL DEFAULT 'pending',\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n idempotency_key text UNIQUE,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT events_topic_format CHECK (topic ~ '^\\w+(:\\w+)*$')\n);\n\n-- Drop legacy columns from earlier schema generations. Single-instance\n-- project; column drops are fine.\nALTER TABLE events DROP COLUMN IF EXISTS supervisor_prompt;\nALTER TABLE events DROP COLUMN IF EXISTS causation_chain;\n\n-- Chat-aware event metadata. `is_chat` opts into automatic user-message\n-- persistence in EventBus.add; `preferred_chat_channel_id` names the\n-- channel an assistant reply should reach the user on. The container is\n-- channel-blind \u2014 both fields are read host-side (for routing) or via\n-- JOIN inside the chatmessages NOTIFY trigger.\nALTER TABLE events ADD COLUMN IF NOT EXISTS is_chat boolean NOT NULL DEFAULT false;\nALTER TABLE events ADD COLUMN IF NOT EXISTS preferred_chat_channel_id text;\n-- Required for non-chat events (validated at `EventBus.add` time and at\n-- the type level via `NewEvent`'s discriminated union); always null for\n-- chat events because the user's text in `chatmessages` already serves\n-- as the agent-visible \"what happened\" message. The EventWatcher copies\n-- this verbatim into the root `agentruns.prompt` so the agent sees the\n-- emitter's framing without the EventWatcher having to reach into the\n-- payload schema.\nALTER TABLE events ADD COLUMN IF NOT EXISTS prompt text;\n-- Optional override for the root agentrun's handler. When NULL the\n-- input-event watcher falls back to 'index' \u2014 same behaviour as\n-- before this column existed. Set by the emitter on NewEvent.\nALTER TABLE events ADD COLUMN IF NOT EXISTS start_handler text;\n-- Privileged events originate from a trusted user-input source (operator\n-- at the local terminal via cli-chat, operator on Telegram). The flag\n-- propagates verbatim to the root agentrun and to every child agentrun\n-- spawned via schedule_subagent / start_subagent, so future system tools can\n-- gate risky reads / writes (editing SOUL.md etc.) on whether the run\n-- descends from a trusted input. Default false: any plugin that doesn't\n-- explicitly opt in produces non-privileged events.\nALTER TABLE events ADD COLUMN IF NOT EXISTS privileged boolean NOT NULL DEFAULT false;\n-- When true and the event ends in 'failed', `HostContextImpl.emitAndAwait`\n-- writes a role='assistant' chatmessage with the failure text before\n-- rejecting handle.settled. Default false: only chat plugins (cli-chat,\n-- telegram) opt in. See [[outputChatOnFailure]] on NewEvent for the\n-- rationale.\nALTER TABLE events ADD COLUMN IF NOT EXISTS output_chat_on_failure boolean NOT NULL DEFAULT false;\n\nCREATE INDEX IF NOT EXISTS events_state_priority_idx\n ON events (state, priority DESC, id ASC);\nCREATE INDEX IF NOT EXISTS events_topic_idx ON events (topic);\n\nCREATE TABLE IF NOT EXISTS agentruns (\n id bigserial PRIMARY KEY,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n parent_agentrun_id bigint REFERENCES agentruns(id) ON DELETE CASCADE,\n topic text NOT NULL,\n handler text NOT NULL,\n priority smallint NOT NULL DEFAULT 50,\n state text NOT NULL DEFAULT 'pending',\n prompt text,\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n result jsonb,\n result_text text,\n error text,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT agentruns_topic_format CHECK (topic ~ '^\\w+(:\\w+)*$')\n);\n\n-- Forward-compat ALTER for databases created before result_text landed.\n-- Safe no-op on fresh databases where the column already exists.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS result_text text;\n-- Inherited from the originating event (root agentrun) or parent\n-- agentrun (children spawned via schedule_subagent / start_subagent). See\n-- the matching ALTER on the events table for the trust-model rationale.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS privileged boolean NOT NULL DEFAULT false;\n-- Number of retry attempts already made on this agentrun. Bumped each\n-- time AgentRunner postpones the row due to a retryable inference\n-- error (APICallError.isRetryable). Capped per-handler via maxRetries\n-- (frontmatter override) or globally via inference.maxRetries.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS retry_count int NOT NULL DEFAULT 0;\n-- Earliest moment the agentrun watcher is allowed to re-claim this\n-- row. NULL = claim immediately (default for fresh rows). A future\n-- timestamp = leave the row alone until then; the watcher's claim\n-- query gates on this so a parked row doesn't waste the slot while\n-- other agentruns (potentially using different models) can still run.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS not_before timestamptz;\n-- Resolved model id for this agentrun, of the form\n-- `<provider>/<modelId>` (e.g. `featherless/zai-org/GLM-5.1`).\n-- Populated by AgentRunner once it builds the model client; stays\n-- NULL for any agentrun that never reached generate() (failed at\n-- handler-load time, etc.).\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS model text;\n-- Resolved prompt (rendered prompt template: system prompt, then\n-- user-message head) used for this agentrun. Populated by AgentRunner\n-- only when core.logSystemPrompt is true; otherwise NULL. Surfaced\n-- by the report layer's Agentrun Start section when the consumer\n-- opts into withDetails.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS system_prompt text;\n-- Verbatim model-message array assembled for the first agent.generate()\n-- call (prior history + the run's leading user turn). Populated by\n-- AgentRunner only when inference.captureInitialMessageHistory is true;\n-- otherwise NULL. Surfaced by the report layer's -vv view so an operator\n-- can see exactly what was sent into the model loop.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS initial_messages jsonb;\n\n-- Distinguishes how a child agentrun was spawned.\n-- NULL \u2014 root agentrun (created by the input-event watcher, no parent).\n-- 'scheduled' \u2014 fire-and-forget child spawned by `schedule_subagent` without\n-- a future `when` (immediate dispatch under the same event).\n-- 'started' \u2014 synchronous child spawned via the `start_subagent` tool; the\n-- parent is parked in state='waiting' until this row settles.\n-- The Scheduler reads this column to decide whether settling a row should\n-- wake a waiting parent.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS calltype text;\nALTER TABLE agentruns DROP CONSTRAINT IF EXISTS agentruns_calltype_format;\nALTER TABLE agentruns ADD CONSTRAINT agentruns_calltype_format\n CHECK (calltype IS NULL OR calltype IN ('scheduled','started'));\n\n-- Partial index for the \"are all started children of $parent settled?\"\n-- check the Scheduler runs after every 'started' child settles. Indexes\n-- only the rows that actually participate in the question.\nCREATE INDEX IF NOT EXISTS agentruns_parent_calltype_state_idx\n ON agentruns (parent_agentrun_id, state)\n WHERE calltype = 'started';\n\n-- Forward-compat: relax the topic CHECK constraint to allow arbitrarily\n-- deep `a:b:c:\u2026` topics. CREATE TABLE IF NOT EXISTS above doesn't\n-- update existing constraints, so we drop+re-add unconditionally. The\n-- DROP IF EXISTS / ADD pair is idempotent.\nALTER TABLE events DROP CONSTRAINT IF EXISTS events_topic_format;\nALTER TABLE events ADD CONSTRAINT events_topic_format CHECK (topic ~ '^\\w+(:\\w+)*$');\nALTER TABLE agentruns DROP CONSTRAINT IF EXISTS agentruns_topic_format;\nALTER TABLE agentruns ADD CONSTRAINT agentruns_topic_format CHECK (topic ~ '^\\w+(:\\w+)*$');\n\nCREATE INDEX IF NOT EXISTS agentruns_state_priority_idx\n ON agentruns (state, priority DESC, id ASC);\nCREATE INDEX IF NOT EXISTS agentruns_event_id_idx ON agentruns (event_id);\nCREATE INDEX IF NOT EXISTS agentruns_parent_idx ON agentruns (parent_agentrun_id);\n\nCREATE TABLE IF NOT EXISTS stepresults (\n id bigserial PRIMARY KEY,\n agent_run_id bigint NOT NULL REFERENCES agentruns(id) ON DELETE CASCADE,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n step_number int NOT NULL,\n finish_reason text NOT NULL,\n result_text text,\n reasoning_text text,\n input_tokens int,\n output_tokens int,\n total_tokens int,\n tool_call_count int NOT NULL DEFAULT 0,\n tool_calls jsonb NOT NULL DEFAULT '[]'::jsonb,\n tool_results jsonb NOT NULL DEFAULT '[]'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT stepresults_step_number_unique UNIQUE (agent_run_id, step_number)\n);\n\nCREATE INDEX IF NOT EXISTS stepresults_agent_run_id_idx ON stepresults (agent_run_id);\nCREATE INDEX IF NOT EXISTS stepresults_event_id_idx ON stepresults (event_id);\n\n-- AI SDK per-step usage breakdown\n-- (LanguageModelUsage.inputTokenDetails / outputTokenDetails). Each\n-- column is NULL when the provider doesn't report that detail.\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_no_cache int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_cache_read int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_cache_write int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS output_tokens_text int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS output_tokens_reasoning int;\n-- Full step object snapshot, populated only when the operator opts\n-- in via inference.captureRawStepResultToDatabase: true. Used to\n-- inspect provider-specific fields (Anthropic cache control,\n-- OpenAI logprobs, ...) that don't fit dedicated columns.\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS raw_result jsonb;\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 chatmessages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Persistent chat history across all channels. Each row links to the\n-- event whose lifecycle the message belongs to:\n-- * For role='user': the event the host plugin emitted with isChat=true.\n-- * For role='assistant': the event whose agentrun called send_chat.\n-- The channel is reachable via JOIN to events.preferred_chat_channel_id;\n-- there is intentionally no channel_id column here \u2014 channel routing is\n-- a host-side concern, the container never names a channel.\n--\n-- delivered_at is set when ANY listener returns true from its async\n-- handler. New subscribers replay all undelivered matching rows on\n-- registration so a message produced when nobody was listening is still\n-- delivered later.\nCREATE TABLE IF NOT EXISTS chatmessages (\n id bigserial PRIMARY KEY,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n role text NOT NULL CHECK (role IN ('user','assistant')),\n text_content text NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n delivered_at timestamptz\n);\n\nCREATE INDEX IF NOT EXISTS chatmessages_event_idx\n ON chatmessages (event_id);\nCREATE INDEX IF NOT EXISTS chatmessages_undelivered_idx\n ON chatmessages (created_at) WHERE delivered_at IS NULL;\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 NOTIFY triggers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCREATE OR REPLACE FUNCTION events_notify_new() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify('events_new', NEW.id::text);\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS events_new_trg ON events;\nCREATE TRIGGER events_new_trg\n AFTER INSERT ON events\n FOR EACH ROW EXECUTE FUNCTION events_notify_new();\n\nCREATE OR REPLACE FUNCTION events_notify_state() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify('events_state', NEW.id::text || ':' || NEW.state);\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS events_state_trg ON events;\nCREATE TRIGGER events_state_trg\n AFTER UPDATE OF state ON events\n FOR EACH ROW EXECUTE FUNCTION events_notify_state();\n\nCREATE OR REPLACE FUNCTION agentruns_notify_changed() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify(\n 'agentruns_changed',\n NEW.event_id::text || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS agentruns_notify_changed_trg ON agentruns;\nCREATE TRIGGER agentruns_notify_changed_trg\n AFTER INSERT OR UPDATE OF state ON agentruns\n FOR EACH ROW EXECUTE FUNCTION agentruns_notify_changed();\n\nCREATE OR REPLACE FUNCTION stepresults_notify_new() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify(\n 'stepresults_new',\n NEW.event_id::text || ':' || NEW.agent_run_id::text || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS stepresults_new_trg ON stepresults;\nCREATE TRIGGER stepresults_new_trg\n AFTER INSERT ON stepresults\n FOR EACH ROW EXECUTE FUNCTION stepresults_notify_new();\n\n-- chatmessages NOTIFY: payload is \"<channel_id>:<role>:<id>\". Channel\n-- comes from the parent event's preferred_chat_channel_id, JOINed at\n-- trigger time. Empty channel becomes empty prefix \u2014 listeners will\n-- filter it out unless they explicitly subscribe to channelId=\"\".\nCREATE OR REPLACE FUNCTION chatmessages_notify_new() RETURNS trigger AS $$\nDECLARE\n channel text;\nBEGIN\n SELECT preferred_chat_channel_id INTO channel FROM events WHERE id = NEW.event_id;\n PERFORM pg_notify(\n 'chatmessages_new',\n COALESCE(channel, '') || ':' || NEW.role || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS chatmessages_new_trg ON chatmessages;\nCREATE TRIGGER chatmessages_new_trg\n AFTER INSERT ON chatmessages\n FOR EACH ROW EXECUTE FUNCTION chatmessages_notify_new();\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 calendars + calendar_events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Plugin-agnostic calendar data layer. `calendars` lists every\n-- subscription (own or shared) any provider plugin (ms365, future\n-- google/caldav) has surfaced; `calendar_events` stores every\n-- instance. Providers persist every occurrence of a recurring\n-- series independently (no master-expansion in core).\n--\n-- Identity:\n-- * `calendars.unique_key` is the provider's stable id. Uniqueness\n-- within `(plugin_id, unique_key)` lets multiple providers\n-- coexist.\n-- * `calendar_events.id` is provider-prefixed (e.g. `ms365:<graph-id>`)\n-- so collisions across providers are impossible by construction.\n--\n-- Refresh reconciliation uses a scan-generation tag: a refresh walk\n-- bumps `calendars.scan_generation`, upserts every event in window\n-- with the new value, then DELETEs events whose generation is older\n-- than the calendar's current value.\n\nCREATE TABLE IF NOT EXISTS calendars (\n id bigserial PRIMARY KEY,\n plugin_id text NOT NULL,\n unique_key text NOT NULL,\n name text NOT NULL,\n type text NOT NULL CHECK (type IN ('own','shared')),\n owner_name text,\n scan_generation int NOT NULL DEFAULT 0,\n created_at timestamptz NOT NULL DEFAULT now(),\n UNIQUE (plugin_id, unique_key)\n);\n\n-- The provider's own \"default calendar\" flag (Graph's\n-- `isDefaultCalendar`). Used by `CalendarService.resolveDefaultCalendar`\n-- as the fallback when `core.defaultCalendar` is unset \u2014 we pick the\n-- first registered calendar (lowest id) flagged here. Added via\n-- ALTER so existing databases don't need a schema reset.\nALTER TABLE calendars ADD COLUMN IF NOT EXISTS is_default smallint NOT NULL DEFAULT 0;\n\nCREATE INDEX IF NOT EXISTS calendars_plugin_id_idx ON calendars (plugin_id);\n\nCREATE TABLE IF NOT EXISTS calendar_events (\n id text PRIMARY KEY,\n calendar_id bigint NOT NULL REFERENCES calendars(id) ON DELETE CASCADE,\n series_master_id text,\n type text NOT NULL CHECK (type IN ('singleInstance','occurrence','exception','seriesMaster')),\n subject text,\n start_dt text NOT NULL,\n end_dt text NOT NULL,\n event_tz text,\n is_all_day smallint NOT NULL DEFAULT 0,\n is_cancelled smallint NOT NULL DEFAULT 0,\n show_as text,\n sensitivity text,\n importance text,\n location text,\n is_online_meeting smallint NOT NULL DEFAULT 0,\n online_meeting_url text,\n organizer_name text,\n organizer_email text,\n response_status text,\n attendees_json jsonb,\n body text,\n attachments jsonb,\n scan_generation int NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS calendar_events_window_idx\n ON calendar_events (calendar_id, start_dt);\nCREATE INDEX IF NOT EXISTS calendar_events_series_idx\n ON calendar_events (series_master_id);\nCREATE INDEX IF NOT EXISTS calendar_events_generation_idx\n ON calendar_events (calendar_id, scan_generation);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 scheduled_subagents \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- One-off agent-scheduled wake-ups. Each row is upserted by the\n-- container's `schedule_subagent` tool and read by the host-side\n-- `ScheduledSubagentScheduler`, which installs a Croner job per row\n-- and atomically deletes the row when it fires. `key` is the\n-- agent-supplied unique id used for replace-on-conflict semantics\n-- (\"rescheduling a key overwrites the previous timing\"). `fire_at`\n-- is the UTC firing time; the agent passes wall-clock-in-`core.timezone`\n-- and the container projects to UTC before insert. On daemon startup,\n-- past-due rows are deleted with a warn log; future rows are\n-- reinstalled. See [[feedback_agent_facing_format]] \u2014 UTC storage,\n-- local-tz projection at every agent-facing edge.\nCREATE TABLE IF NOT EXISTS scheduled_subagents (\n key text PRIMARY KEY,\n fire_at timestamptz NOT NULL,\n topic text NOT NULL,\n handler text NOT NULL,\n prompt text,\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n priority smallint NOT NULL DEFAULT 50,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT scheduled_subagents_topic_format CHECK (topic ~ '^\\w+(:\\w+)*$')\n);\n\nCREATE INDEX IF NOT EXISTS scheduled_subagents_fire_at_idx\n ON scheduled_subagents (fire_at);\n\nCREATE OR REPLACE FUNCTION scheduled_subagents_notify_changed() RETURNS trigger AS $$\nDECLARE\n payload_key text;\n op text;\nBEGIN\n IF TG_OP = 'DELETE' THEN\n payload_key := OLD.key;\n op := 'd';\n ELSE\n payload_key := NEW.key;\n op := 'u';\n END IF;\n PERFORM pg_notify('scheduled_subagents_changed', payload_key || ':' || op);\n RETURN COALESCE(NEW, OLD);\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS scheduled_subagents_notify_changed_trg ON scheduled_subagents;\nCREATE TRIGGER scheduled_subagents_notify_changed_trg\n AFTER INSERT OR UPDATE OR DELETE ON scheduled_subagents\n FOR EACH ROW EXECUTE FUNCTION scheduled_subagents_notify_changed();\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 inference_events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Append-only audit trail of every upstream model HTTP call attempt.\n-- Written by AgentRunner on each generate() outcome (success / retryable /\n-- fatal). Read by the host-side `inference_status` reflection tool to\n-- answer \"is this model misbehaving right now?\". One row per attempt;\n-- retries write multiple rows. NULL agent_run_id is kept for runs whose\n-- row was later deleted (parent event cascade) so the historical signal\n-- survives.\nCREATE TABLE IF NOT EXISTS inference_events (\n id bigserial PRIMARY KEY,\n provider text NOT NULL,\n model text NOT NULL,\n agent_run_id bigint REFERENCES agentruns(id) ON DELETE SET NULL,\n outcome text NOT NULL CHECK (outcome IN ('success','retryable','fatal')),\n status_code int,\n error_excerpt text,\n occurred_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS inference_events_model_time_idx\n ON inference_events (model, occurred_at DESC);\nCREATE INDEX IF NOT EXISTS inference_events_occurred_at_idx\n ON inference_events (occurred_at DESC);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 tool_calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Append-only record of every tool invocation an agentrun attempted,\n-- tagged with the resolved handler markdown path and whether the call\n-- threw. Written by the tool wrapper in the container's ToolsFactory on\n-- each execute() outcome. Read by the heuristic tool-preloader\n-- (ToolCallBus.topToolsForHandler): the tools a handler successfully\n-- used across its recent non-error runs get loaded up front on the next\n-- run, so warm handlers rarely need a tool_list / tool_call round-trip.\n-- NULL agent_run_id survives a parent-event cascade so the historical\n-- usage signal outlives the run row.\nCREATE TABLE IF NOT EXISTS tool_calls (\n id bigserial PRIMARY KEY,\n agent_run_id bigint REFERENCES agentruns(id) ON DELETE SET NULL,\n handler_path text NOT NULL,\n tool_name text NOT NULL,\n successful boolean NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS tool_calls_handler_run_idx\n ON tool_calls (handler_path, agent_run_id DESC);\nCREATE INDEX IF NOT EXISTS tool_calls_created_at_idx\n ON tool_calls (created_at DESC);\n";
64
64
  /**
65
65
  * SQL fragment that flips the parent event's terminal state when the
66
66
  * **root** agentrun settles. Run inside the same transaction as the
package/build/Schema.js CHANGED
@@ -171,8 +171,8 @@ ALTER TABLE agentruns ADD COLUMN IF NOT EXISTS not_before timestamptz;
171
171
  -- NULL for any agentrun that never reached generate() (failed at
172
172
  -- handler-load time, etc.).
173
173
  ALTER TABLE agentruns ADD COLUMN IF NOT EXISTS model text;
174
- -- Resolved system prompt (SOUL.md + CONTEXT.md + handler body +
175
- -- tool list) used for this agentrun. Populated by AgentRunner
174
+ -- Resolved prompt (rendered prompt template: system prompt, then
175
+ -- user-message head) used for this agentrun. Populated by AgentRunner
176
176
  -- only when core.logSystemPrompt is true; otherwise NULL. Surfaced
177
177
  -- by the report layer's Agentrun Start section when the consumer
178
178
  -- opts into withDetails.
package/build/index.d.ts CHANGED
@@ -22,9 +22,10 @@ export { renderMarkdownToHtml } from "./markdownHtml.js";
22
22
  export { escapeTableCell, markdownTable, writeMarkdown } from "./markdownOutput.js";
23
23
  export { renderMarkdown } from "./markdownTerminal.js";
24
24
  export { matchesAnyGlob, matchesGlob } from "./PathGlob.js";
25
- export type { AnyCommandDef, EmitHandle, EmitOptions, EventContextProvider, HostContext, McpClient, McpInfo, PluginHostManifest, PluginManifest, PluginTool, PluginToolCallContext, } from "./Plugin.js";
25
+ export type { AnyCommandDef, EmitHandle, EmitOptions, HostContext, McpClient, McpInfo, PluginHostManifest, PluginManifest, PluginTool, PluginToolCallContext, PromptAppender, PromptPlaceholderRegistration, } from "./Plugin.js";
26
26
  export { DaemonStoppedError, definePlugin } from "./Plugin.js";
27
27
  export { type NotificationHandler, POSTGRES_DB, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, PostgresConnection, type PostgresConnectionConfig, } from "./PostgresConnection.js";
28
+ export { CACHE_MARKER_PLACEHOLDER, CORE_PROMPT_PLACEHOLDERS, isCorePromptPlaceholder, PROMPT_PLACEHOLDER_NAME_PATTERN, PROMPT_TEMPLATE_FILE, PromptPlaceholderConflictError, type PromptPlaceholderDefinition, type PromptPlaceholderKind, } from "./PromptPlaceholders.js";
28
29
  export type { NewScheduledSubagent, ScheduledSubagentRow } from "./ScheduledSubagent.js";
29
30
  export { ScheduledSubagentBus, type ScheduledSubagentNotification, type ScheduledSubagentOp, type ScheduledSubagentUnsubscribe, } from "./ScheduledSubagentBus.js";
30
31
  export { AGENTRUNS_CHANNEL, CHATMESSAGES_NEW_CHANNEL, EVENT_TERMINAL_UPDATE_SQL, EVENTS_NEW_CHANNEL, EVENTS_STATE_CHANNEL, SCHEDULED_SUBAGENTS_CHANNEL, SCHEMA_SQL, STEPRESULTS_NEW_CHANNEL, TOPIC_PATTERN, } from "./Schema.js";
package/build/index.js CHANGED
@@ -18,6 +18,7 @@ export { renderMarkdown } from "./markdownTerminal.js";
18
18
  export { matchesAnyGlob, matchesGlob } from "./PathGlob.js";
19
19
  export { DaemonStoppedError, definePlugin } from "./Plugin.js";
20
20
  export { POSTGRES_DB, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, PostgresConnection, } from "./PostgresConnection.js";
21
+ export { CACHE_MARKER_PLACEHOLDER, CORE_PROMPT_PLACEHOLDERS, isCorePromptPlaceholder, PROMPT_PLACEHOLDER_NAME_PATTERN, PROMPT_TEMPLATE_FILE, PromptPlaceholderConflictError, } from "./PromptPlaceholders.js";
21
22
  export { ScheduledSubagentBus, } from "./ScheduledSubagentBus.js";
22
23
  export { AGENTRUNS_CHANNEL, CHATMESSAGES_NEW_CHANNEL, EVENT_TERMINAL_UPDATE_SQL, EVENTS_NEW_CHANNEL, EVENTS_STATE_CHANNEL, SCHEDULED_SUBAGENTS_CHANNEL, SCHEMA_SQL, STEPRESULTS_NEW_CHANNEL, TOPIC_PATTERN, } from "./Schema.js";
23
24
  export { StepResultBus } from "./StepResultBus.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@getfamiliar/shared",
3
3
  "type": "module",
4
- "version": "0.1.10",
4
+ "version": "0.1.11",
5
5
  "description": "Familiar — shared types and bus-state client between host and container.",
6
6
  "publishConfig": {
7
7
  "access": "public"