palbase 0.7.0 → 0.8.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 (76) hide show
  1. package/dist/ai/index.js +2 -2
  2. package/dist/backend/engine.js +2 -2
  3. package/dist/backend/env.js +1 -1
  4. package/dist/backend/index.js +9 -9
  5. package/dist/backend/stack.js +1 -1
  6. package/dist/backend/test.js +2 -2
  7. package/dist/chunks/5VTGMNPL.js +20 -0
  8. package/dist/chunks/{ZBOJEH7K.js → BO5KR2EQ.js} +2 -2
  9. package/dist/chunks/{PJVWGUIT.js → BRBCWPR3.js} +1 -1
  10. package/dist/chunks/DB3SIBKB.js +2 -0
  11. package/dist/chunks/EGNEXFTZ.js +2 -0
  12. package/dist/chunks/{HEXIIGWS.js → FZ3MQOVA.js} +2 -2
  13. package/dist/chunks/{SU7BP6VF.js → JSJVV43D.js} +1 -1
  14. package/dist/chunks/K6RRNI23.js +4 -0
  15. package/dist/chunks/{KDSWK3XW.js → KDWDPSL2.js} +1 -1
  16. package/dist/chunks/P4Z5E6ZI.js +2 -0
  17. package/dist/chunks/PDOHT46N.js +2 -0
  18. package/dist/chunks/PGPQGKH2.js +18 -0
  19. package/dist/chunks/{G3BK7L3J.js → RBA6V3UO.js} +2 -2
  20. package/dist/chunks/{X3MTLJRV.js → RO5Y5YU4.js} +1 -1
  21. package/dist/chunks/{BKVU4I5R.js → TUGRIOFX.js} +2 -2
  22. package/dist/chunks/WEI32SAJ.js +13 -0
  23. package/dist/chunks/{5ZJCEQV5.js → WINBISC2.js} +3 -3
  24. package/dist/chunks/Z5JVQNAZ.js +2 -0
  25. package/dist/cli.js +121 -120
  26. package/dist/refuse/ai.js +1 -1
  27. package/dist/refuse/backend.js +1 -1
  28. package/dist/refuse/server.js +1 -1
  29. package/dist/server/index.js +2 -2
  30. package/dist/server/testing.js +2 -2
  31. package/dist/web/index.js +2 -2
  32. package/dist/web/palbe.js +2 -2
  33. package/dist/web/paywall.js +2 -2
  34. package/dist/web/ssr.js +2 -2
  35. package/dist/web/vite.js +2 -2
  36. package/package.json +2 -2
  37. package/types/ai/src/ai.d.ts +44 -15
  38. package/types/ai/src/chats.d.ts +442 -12
  39. package/types/ai/src/index.d.ts +4 -4
  40. package/types/ai/src/tools.d.ts +158 -21
  41. package/types/backend/src/bootstrap.d.ts +8 -0
  42. package/types/backend/src/container.d.ts +9 -0
  43. package/types/backend/src/database.d.ts +17 -2
  44. package/types/backend/src/engine.d.ts +2 -0
  45. package/types/backend/src/gateways.d.ts +19 -0
  46. package/types/backend/src/index.d.ts +1 -0
  47. package/types/backend/src/runtime.d.ts +12 -0
  48. package/types/backend/src/testing/unit.d.ts +3 -1
  49. package/types/internal/src/chat.d.ts +168 -3
  50. package/types/internal/src/modules/ai.d.ts +1305 -57
  51. package/types/internal/src/modules/auth.d.ts +14 -2
  52. package/types/internal/src/modules/events.d.ts +2 -2
  53. package/types/internal/src/modules/machines.d.ts +57 -1
  54. package/types/internal/src/modules/notify.d.ts +6 -6
  55. package/types/internal/src/modules/purchases.d.ts +357 -13
  56. package/types/internal/src/modules/realtime.d.ts +36 -0
  57. package/types/internal/src/modules/stores.d.ts +259 -22
  58. package/types/internal/src/transport.d.ts +5 -0
  59. package/types/server/src/auth/jwt.d.ts +1 -6
  60. package/types/web/src/auth.d.ts +15 -4
  61. package/types/web/src/backends.d.ts +1 -1
  62. package/types/web/src/chat.d.ts +215 -4
  63. package/types/web/src/client.d.ts +28 -0
  64. package/types/web/src/gateways.d.ts +4 -3
  65. package/types/web/src/index.d.ts +3 -2
  66. package/types/web/src/session.d.ts +11 -1
  67. package/types/web/src/voice.d.ts +36 -0
  68. package/dist/chunks/3ALGILOX.js +0 -2
  69. package/dist/chunks/AIIKXCWN.js +0 -2
  70. package/dist/chunks/ELEE7XO2.js +0 -18
  71. package/dist/chunks/G7E26S3Y.js +0 -13
  72. package/dist/chunks/H2EPK74V.js +0 -20
  73. package/dist/chunks/JS3TTUHS.js +0 -2
  74. package/dist/chunks/L3NTI4AZ.js +0 -2
  75. package/dist/chunks/LEBVOSG6.js +0 -4
  76. package/dist/chunks/OIA7KLRW.js +0 -2
@@ -25,12 +25,58 @@ export interface ChatContext {
25
25
  metadata: Record<string, string>;
26
26
  /** Ended when the platform stops waiting for the answer. */
27
27
  signal: AbortSignal;
28
+ /**
29
+ * The app's instance of a provider — a class a module of the app lists in
30
+ * its providers, built once by its container with its constructor's
31
+ * dependencies — as a constructor's parameter of that type is given it:
32
+ * ctx.inject(Orders). The app that serves the call gives it; a class no
33
+ * module provides is refused, by name.
34
+ */
35
+ inject<T extends object>(provider: abstract new (...args: never[]) => T): T;
28
36
  }
29
37
  export interface ToolContext extends ChatContext {
30
38
  /** The call's id: the same call is the same id, whatever copy of the machine it reaches. */
31
39
  callId: string;
40
+ /** The turn the call is made in: its own response's id — a subagent's call's too. */
41
+ turnId: string;
42
+ /**
43
+ * The run that made the call: the agent's own, or a subagent's at work. A
44
+ * tool that answers later — it asks a person, and gives their answer to
45
+ * whoever asked — answers that run: chats.sendToSubagent(chatId, runId, …).
46
+ */
47
+ runId: string;
48
+ /** The subagent that made the call, by its name; null: the agent itself. */
49
+ subagent: string | null;
50
+ }
51
+ /** The message that opened a turn, as the backend that defines its agent is told of it. */
52
+ export interface TurnMessage {
53
+ id: string;
54
+ clientId: string;
55
+ /** The member who wrote it; null: the project's server. */
56
+ author: string | null;
57
+ authorName: string;
58
+ text: string;
59
+ delivery: 'next' | 'now' | (string & {});
60
+ /**
61
+ * Its writer's labels for it (chats.send({ metadata })) — what an app's
62
+ * choice for the turn rides on: a model, an effort, the files it names. A
63
+ * member may write them: the caller's word, not an authority.
64
+ */
65
+ metadata: Record<string, string>;
66
+ }
67
+ /** What the backend that defines an agent is told of the turn: the chat's context, and what opened the turn. */
68
+ export interface AgentContext extends ChatContext {
69
+ /** The message that opened the turn; null: a turn of no message (the chat's voice, its goal). */
70
+ message: TurnMessage | null;
71
+ /**
72
+ * The chat's goal the agent is defined for (chats.goal.set): every turn the
73
+ * goal opens is the agent defined now. Null: a turn of a message.
74
+ */
75
+ goal: {
76
+ objective: string;
77
+ rubric: string;
78
+ } | null;
32
79
  }
33
- export type AgentContext = ChatContext;
34
80
  export interface BackendToolOptions<S extends StandardSchema> {
35
81
  /** What the agent calls it by: letters, digits, _ and -, starting with a letter, at most 64. */
36
82
  name: string;
@@ -43,9 +89,11 @@ export interface BackendToolOptions<S extends StandardSchema> {
43
89
  * (`{ mediaType, bytes }`), or a list of them — 4 MiB at most in all. A
44
90
  * throw is the tool's failure: a ToolError's message (or the framework's
45
91
  * own refusals', NotFound('…')) is told to the agent, anything else as
46
- * "The tool failed".
92
+ * "The tool failed". A tool answers within 110 seconds; one whose work
93
+ * takes longer returns ai.deferred() at once — a chat's call waits, and the
94
+ * project's server answers it later with chats.answer(ctx.chatId, ctx.callId, output).
47
95
  */
48
- run(input: Infer<S>, ctx: ToolContext): ToolResult | Promise<ToolResult>;
96
+ run(input: Infer<S>, ctx: ToolContext): ToolResult | DeferredAnswer | Promise<ToolResult | DeferredAnswer>;
49
97
  /** Every call waits for a person's yes (a member of the chat, or the server). */
50
98
  approval?: boolean;
51
99
  /** The project's machine it runs on (default backend). */
@@ -60,12 +108,34 @@ export interface BackendTool<S extends StandardSchema = StandardSchema> {
60
108
  readonly parameters: Record<string, unknown>;
61
109
  readonly approval: boolean;
62
110
  readonly machine?: string;
63
- /** The run, its input checked first: what the framework answers the platform's call with. */
64
- call(input: unknown, ctx: ToolContext): Promise<{
65
- output: ToolContent[];
66
- is_error: boolean;
67
- }>;
111
+ /** The run, its input checked first: what the framework answers the platform's call with — an answer, or one to come (deferred). */
112
+ call(input: unknown, ctx: ToolContext): Promise<ToolAnswer>;
68
113
  }
114
+ /** A backend tool's answer as the platform takes it: now (output), or later — the project's server gives it (chats.answer). */
115
+ export type ToolAnswer = {
116
+ output: ToolContent[];
117
+ is_error: boolean;
118
+ } | {
119
+ deferred: true;
120
+ expires_in_seconds?: number;
121
+ };
122
+ declare const DEFERRED: unique symbol;
123
+ /** What a backend tool's run returns when its answer comes later: ai.deferred(). */
124
+ export interface DeferredAnswer {
125
+ readonly [DEFERRED]: true;
126
+ /** How long the call waits for its answer: a minute to a day; left out, an hour. */
127
+ readonly expiresInSeconds?: number;
128
+ }
129
+ /**
130
+ * The answer comes later: a chat's call waits for it — the turn too, as for
131
+ * an app's tool — and the project's server gives it with chats.answer(chatId,
132
+ * callId, output) within expiresInSeconds (a minute to a day; an hour). A
133
+ * call nobody answers by then is told to the agent as unanswered; no member
134
+ * answers it. A one-shot answer (ai.ask with tools) waits for no call.
135
+ */
136
+ export declare function deferred(options?: {
137
+ expiresInSeconds?: number;
138
+ }): DeferredAnswer;
69
139
  export interface ClientToolOptions<S extends StandardSchema> {
70
140
  name: string;
71
141
  description: string;
@@ -94,7 +164,23 @@ export interface McpToolRef {
94
164
  only?: string[];
95
165
  approval?: boolean;
96
166
  }
97
- export type Tool = BackendTool<StandardSchema> | ClientTool | HttpsToolRef | McpToolRef;
167
+ /** One of the platform's own tools, by name. */
168
+ export type PlatformToolName = 'web.search' | 'web.fetch' | 'workspace.fetch_archive';
169
+ /**
170
+ * One of the platform's own tools: web.search (the web searched), web.fetch
171
+ * (a page of it read), workspace.fetch_archive (an archive at a URL unpacked
172
+ * into the chat's workspace — a workspace's chat has it already). The
173
+ * agent's alone: its subagents work with the agent's; a one-shot answer has none.
174
+ */
175
+ export interface PlatformToolRef {
176
+ source: 'platform';
177
+ name: PlatformToolName;
178
+ }
179
+ export type Tool = BackendTool<StandardSchema> | ClientTool | HttpsToolRef | McpToolRef | PlatformToolRef;
180
+ /** How hard a model thinks; which rungs a model takes is its catalogue entry's (ai.models(): chat[].efforts). */
181
+ export type Effort = NonNullable<AgentDefinition['effort']>;
182
+ /** Whether a model's reasoning is shown in the chat's stream (thinking parts) or not asked for at all. */
183
+ export type Thinking = NonNullable<AgentDefinition['thinking']>;
98
184
  export interface SubagentDefinition {
99
185
  /** Unique among the agent's subagents. */
100
186
  name: string;
@@ -105,6 +191,12 @@ export interface SubagentDefinition {
105
191
  tools?: Tool[];
106
192
  /** The turn fails if this subagent's part fails. */
107
193
  required?: boolean;
194
+ /** Left out: the agent's, when it runs on the agent's model (another model's rungs mean other things). */
195
+ effort?: Effort;
196
+ /** The window, in tokens, it works in. Left out: the agent's, when it runs on the agent's model. */
197
+ contextWindow?: number;
198
+ /** Left out: the agent's. Its reasoning is never in the chat's stream: only the turn's own is. */
199
+ thinking?: Thinking;
108
200
  }
109
201
  /** What an agent is for a turn. */
110
202
  export interface AgentDefinitionInput {
@@ -114,8 +206,22 @@ export interface AgentDefinitionInput {
114
206
  tools?: Tool[];
115
207
  /** 16 at most. */
116
208
  subagents?: SubagentDefinition[];
117
- /** How many tool calls a turn makes at most (1–200): past it the agent is told it has no tools left and answers. */
209
+ /**
210
+ * How many tool calls a turn makes at most (1–10 000, the engine's own bound): past it the agent is told it has no
211
+ * tools left and answers. What a turn spends is its budget's to bound (chats.send({ budget })), not this.
212
+ */
118
213
  maxToolCalls?: number;
214
+ /** How hard the model thinks (none … max). Left out: the platform's. */
215
+ effort?: Effort;
216
+ /**
217
+ * The window, in tokens, the turn works in: the model's standard one unless said; more is billed as its provider
218
+ * bills it (ai.models(): chat[].context_window and max_input_tokens).
219
+ */
220
+ contextWindow?: number;
221
+ /** visible: the model's reasoning is in the chat's stream, as thinking parts (the turn's own); hidden: not asked for. Left out: the platform's. */
222
+ thinking?: Thinking;
223
+ /** The language of the person the turn works for, a BCP 47 tag (tr, en-GB): the model writes to them in it on every step, and so do its subagents. */
224
+ language?: string;
119
225
  }
120
226
  /** What a backend's agent is for one turn. */
121
227
  export type TurnDefinition = AgentDefinitionInput;
@@ -131,6 +237,24 @@ export interface BackendAgent {
131
237
  tools: BackendTool[];
132
238
  }>;
133
239
  }
240
+ /** What one clone or one push of a chat's workspace asks a repository's token with: an ai.repo's token, ai.repoTokens' give. */
241
+ export interface RepoTokenContext {
242
+ /** The repository's name in the project (ai.repos.put). */
243
+ repo: string;
244
+ /** The GitHub repository, owner/name. */
245
+ repository: string;
246
+ /** Ended when the platform stops waiting for the token. */
247
+ signal: AbortSignal;
248
+ /**
249
+ * The app's instance of a provider, as a tool's run is given it
250
+ * (ChatContext.inject): a token a service of the app's mints — its GitHub
251
+ * App's, its sealed credentials' — from the container of the app that
252
+ * serves the ask. A class no module provides is refused, by name.
253
+ */
254
+ inject<T extends object>(provider: abstract new (...args: never[]) => T): T;
255
+ }
256
+ /** What gives a repository's token when no ai.repo of its name was made: ai.repoTokens(give). */
257
+ export type RepoTokens = (ctx: RepoTokenContext) => string | null | undefined | Promise<string | null | undefined>;
134
258
  /** A repository a chat's workspace works in, whose tokens the backend gives (ai.repo). */
135
259
  export interface BackendRepo {
136
260
  readonly kind: 'repo';
@@ -141,18 +265,16 @@ export interface BackendRepo {
141
265
  /** The project's machine that gives its tokens (default backend). */
142
266
  readonly machine?: string;
143
267
  /** A token for one clone or one push of it: asked at each — a GitHub App's installation token, minted then. */
144
- token(ctx: {
145
- repo: string;
146
- repository: string;
147
- signal: AbortSignal;
148
- }): Promise<string>;
268
+ token(ctx: RepoTokenContext): Promise<string>;
149
269
  }
150
270
  /** What this process declared, for the framework to serve. */
151
271
  export interface DeclaredAI {
152
272
  tools: Map<string, BackendTool>;
153
273
  agents: Map<string, BackendAgent>;
154
274
  repos: Map<string, BackendRepo>;
155
- /** Names declared twice (tool:<name>, agent:<name>): the framework refuses to start with one. */
275
+ /** The tokens of a repository put by name at run time, which no ai.repo of this process names. */
276
+ repoTokens?: RepoTokens;
277
+ /** Names declared twice (tool:<name>, agent:<name>, repoTokens): the framework refuses to start with one. */
156
278
  twice: Set<string>;
157
279
  }
158
280
  /**
@@ -181,17 +303,32 @@ export declare function backendRepo(options: {
181
303
  repository: string;
182
304
  defaultBranch?: string;
183
305
  machine?: string;
184
- token: (ctx: {
185
- repo: string;
186
- repository: string;
187
- signal: AbortSignal;
188
- }) => string | Promise<string>;
306
+ token: (ctx: RepoTokenContext) => string | Promise<string>;
189
307
  }): BackendRepo;
308
+ /**
309
+ * The tokens of the project's repositories put by name at run time
310
+ * (ai.repos.put(name, { repository, backend })) — one a product makes for
311
+ * itself — which no ai.repo of this backend names: each clone and each push
312
+ * of a chat's workspace asks this machine, and give answers with a token for
313
+ * that one operation, or null for a repository it gives none for (the
314
+ * workspace is refused it). An ai.repo of the name answers for its own
315
+ * repository first. Given once, at a module's top level.
316
+ */
317
+ export declare function repoTokens(give: RepoTokens): void;
190
318
  export declare function backendAgent(name: string, define: (ctx: AgentContext) => TurnDefinition | Promise<TurnDefinition>, options?: {
191
319
  machine?: string;
192
320
  }): BackendAgent;
321
+ /** A backend's agent (an ai.agent's value) — its name and machine — or undefined for anything else. */
322
+ export declare function backendAgentOf(v: unknown): {
323
+ readonly name: string;
324
+ readonly machine?: string;
325
+ } | undefined;
193
326
  /** A tool as the platform takes it (ToolRef). */
194
327
  export declare function toolRef(t: Tool, what: string): ToolRef;
328
+ /** The most tool calls a turn or a one-shot answer makes: the engine's own bound. */
329
+ export declare const MAX_TOOL_CALLS = 10000;
330
+ /** A definition's bound on its tool calls, refused here as the module would refuse it. */
331
+ export declare function maxToolCallsOf(n: unknown, what: string): number;
195
332
  /** A definition as the platform takes it (AgentDefinition). */
196
333
  export declare function definitionOf(d: AgentDefinitionInput, what: string): AgentDefinition;
197
334
  export {};
@@ -70,3 +70,11 @@ export declare class MigrationRunEnded extends Error {
70
70
  readonly code: number;
71
71
  constructor(code: number);
72
72
  }
73
+ /**
74
+ * The app's CORS joined to the origins of its project's web apps, as the
75
+ * platform starts it with them (PALBASE_WEB_ORIGINS, comma-separated): a
76
+ * browser on one of them may call it without the app naming it. Any origin
77
+ * ("*") stays any. An origin CORS cannot hold (an address in brackets) is
78
+ * left out, and said.
79
+ */
80
+ export declare function withWebOrigins(own: CorsOptions | undefined, given: string | undefined, warn?: (message: string) => void): CorsOptions | undefined;
@@ -14,6 +14,8 @@ export declare class Container {
14
14
  private readonly owner;
15
15
  private readonly defs;
16
16
  private readonly instances;
17
+ /** The classes the modules list as providers (not controllers): what ctx.inject may be given. */
18
+ private readonly providers;
17
19
  /** Construction order: dependencies before their dependents. */
18
20
  readonly order: object[];
19
21
  readonly controllers: {
@@ -41,6 +43,13 @@ export declare class Container {
41
43
  * a route's @Resolve, by its controller — under a constructor's rules.
42
44
  */
43
45
  dependencyOf<T extends object>(consumer: abstract new (...args: never[]) => object, dep: abstract new (...args: never[]) => T, at: string): T;
46
+ /**
47
+ * The built instance of a provider, asked for by code no module owns — a
48
+ * tool's run, an agent's function (ctx.inject) — so any module's provider,
49
+ * exported or not: the class itself, or the one provider that extends it,
50
+ * as a constructor's parameter of that type finds it. `at` says who asked.
51
+ */
52
+ provider<T extends object>(cls: abstract new (...args: never[]) => T, at: string): T;
44
53
  private reachable;
45
54
  private target;
46
55
  /** onStart() of every instance, in construction order; a throw stops the boot after the started ones are shut down. */
@@ -54,7 +54,7 @@ export interface DatabaseConfig {
54
54
  * nothing — `palbase db:plan` and `db:status` ask the app's own Palgres what a
55
55
  * start would do and what the database's ledger says.
56
56
  */
57
- export type MigrateMode = 'start' | 'complete' | 'rollback' | 'plan' | 'status';
57
+ export type MigrateMode = 'start' | 'complete' | 'rollback' | 'plan' | 'status' | 'types';
58
58
  /** What the deploy asked of this run: a migration step, or nothing (serve). */
59
59
  export declare function migrateModeOf(env: Record<string, string | undefined>): MigrateMode | undefined;
60
60
  /**
@@ -106,9 +106,24 @@ export type MigrationExit = 0 | 1 | 75;
106
106
  * given to such a machine with its migrations on is refused as its release
107
107
  * starts, and the release's rollback then ends — the machine is not held by it.
108
108
  */
109
- export declare function migrateWithoutDatabase(mode: MigrateMode): MigrationExit;
109
+ export declare function migrateWithoutDatabase(mode: MigrateMode): Promise<MigrationExit>;
110
+ /**
111
+ * The app's tables as types (palbase db:types): each table's row, insert, unique keys and relations both ways —
112
+ * across its schema files, which a schema's own type cannot see (TablesOf) — as the block the CLI writes into
113
+ * palbase/palbase-env.d.ts. No database is asked: the schema files say it all.
114
+ */
115
+ export declare function writeTypes(config: DatabaseConfig | undefined, out?: AnswerOut): Promise<MigrationExit>;
110
116
  /** The deploy's migration step. */
111
117
  export declare function runMigration(config: DatabaseConfig, env: Record<string, string | undefined>, mode: MigrateMode): Promise<MigrationExit>;
118
+ /** Where the step's answer is written: the standard output's stream, done once what it was given is out. */
119
+ export type AnswerOut = (text: string, done: () => void) => void;
120
+ /**
121
+ * The step's answer, as one line on stdout the CLI reads: {"palbase_migrate": mode, "result": …} — out, and every
122
+ * log line before it, when this resolves: the run exits right after, and on a pipe what the stream still held went
123
+ * with the process (a large plan's line was cut at what the pipe took: the CLI read "Unterminated string in JSON at
124
+ * position 131072"). One stream, in order: a log line is never cut by it.
125
+ */
126
+ export declare function said(mode: MigrateMode, result: unknown, out?: AnswerOut): Promise<void>;
112
127
  export interface OpenDatabase {
113
128
  model: Schema;
114
129
  binding: Binding;
@@ -138,3 +138,5 @@ export interface App {
138
138
  }
139
139
  export declare const MAX_BODY_BYTES: number;
140
140
  export declare function createApp(opts: AppOptions): App;
141
+ /** An origin CORS holds: https://host[:port], http only for localhost, no path. */
142
+ export declare function corsOrigin(origin: string): boolean;
@@ -205,6 +205,21 @@ export declare function matchChannel(pattern: string, channel: string): Record<s
205
205
  export declare function channelOf(pattern: string, params: Readonly<Record<string, unknown>>): string | {
206
206
  problem: string;
207
207
  };
208
+ /** Whose memberships of a gateway's channel are decided again, and whether the channel is over. */
209
+ export interface RevalidateOptions {
210
+ /** Only this user's membership: the member taken out. */
211
+ user?: string;
212
+ /** The channel is over: every member dropped without being asked, its log forgotten — one channel's, every value given. */
213
+ end?: boolean;
214
+ }
215
+ /**
216
+ * What realtime is asked to decide again for a gateway's channels: the one channel when every capture of its pattern
217
+ * has a value, every channel of the pattern with the values given when some do not — or what is wrong with it, as
218
+ * realtime would refuse it.
219
+ */
220
+ export declare function revalidationOf(pattern: string, params: Readonly<Record<string, unknown>>, options: RevalidateOptions): realtime.Revalidation | {
221
+ problem: string;
222
+ };
208
223
  /** One event to a gateway's members, as realtime's broadcast takes it: its channel (topic), and conn_id for one membership alone. */
209
224
  type GatewayBroadcast = realtime.BroadcastMessage;
210
225
  export interface GatewayDeps {
@@ -216,6 +231,8 @@ export interface GatewayDeps {
216
231
  run<T>(user: SignedUser | undefined, where: string, signal: AbortSignal | undefined, work: () => Promise<T>): Promise<T>;
217
232
  /** realtime's POST /realtime/broadcast, as the app (its secret key). */
218
233
  broadcast(messages: GatewayBroadcast[]): Promise<unknown>;
234
+ /** realtime's POST /realtime/revalidate, as the app: the memberships it names decided again, on every replica. */
235
+ revalidate(body: realtime.Revalidation): Promise<unknown>;
219
236
  /** The platform's infrastructure, not the method, failed: the call is answered 503, and realtime asks again. */
220
237
  isOutage(e: unknown): boolean;
221
238
  }
@@ -239,6 +256,8 @@ export interface Gateways {
239
256
  emit(cls: Function, params: Readonly<Record<string, unknown>>, event: string, payload: unknown): void;
240
257
  /** A source piped into a gateway's channel from anywhere in the app (Palbase.gateways.to(…).pipe). */
241
258
  pipe(cls: Function, params: Readonly<Record<string, unknown>>, source: PipeSource): Promise<void>;
259
+ /** A gateway's channel — or its channels of the values given — decided again (Palbase.gateways.to(…).revalidate). */
260
+ revalidate(cls: Function, params: Readonly<Record<string, unknown>>, options?: RevalidateOptions): Promise<void>;
242
261
  /** Every channel let go: what was held is aborted (the app is stopping). */
243
262
  close(): void;
244
263
  }
@@ -4,6 +4,7 @@ export { Task, TaskFailure, Tasks, type EnqueueOptions, type PayloadOf, type Que
4
4
  export { Upload, UploadedObject, type UploadConfig } from './upload.js';
5
5
  export { Authorize, ChatPart, Gateway, GatewayRefusal, OnConnect, OnDisconnect, OnEmpty, OnFirst, Subscribe, type GatewayAuthorizeContext, type GatewayContext, type GatewayEvents, type GatewayGrant, type GatewayMessageContext, type GatewayOptions, type GatewayUser, type PipeSource, } from './gateways.js';
6
6
  export type { GatewaysApi, GatewayTarget } from './runtime.js';
7
+ export type { AgentContext, ToolContext, TurnDefinition, TurnMessage } from '../../ai/src/index.js';
7
8
  export { getWebhookManifest, type WebhookEventHandler, type WebhookMeta } from './webhooks.js';
8
9
  export { DiError, type DiErrorKind } from './container.js';
9
10
  export { BadRequest, Conflict, DeadlockDetected, Forbidden, HttpError, NotFound, PalError, SerializationFailure, TooManyRequests, Unauthorized, UniqueViolation, defineError, type DefinedError, type DefinedErrorWithData, type Envelope, type FieldIssue, } from './errors.js';
@@ -52,9 +52,21 @@ export interface GatewayTarget<G> {
52
52
  pipe(source: {
53
53
  pipe(topic: string): Promise<void>;
54
54
  }): Promise<void>;
55
+ /**
56
+ * Who may be on the channel is decided again now, on every replica of realtime — a decision the gateway made may no
57
+ * longer hold: a member taken out of a chat, a team changed. A member it would no longer let in (its access, its
58
+ * @Authorize) is dropped, their client told the channel closed; their rejoin is decided on the merits. Params may
59
+ * leave captures out: every channel of the gateway with the values given. user: only that user's membership. end:
60
+ * the channel is over — every member dropped without being asked, its log forgotten (one channel, no user).
61
+ */
62
+ revalidate(options?: {
63
+ user?: string;
64
+ end?: boolean;
65
+ }): Promise<void>;
55
66
  }
56
67
  /** The app's gateways, from any service: Palbase.gateways.to(ChatGateway, { chatId }).emit('token', { text }). */
57
68
  export interface GatewaysApi {
69
+ /** A gateway's channel, by its pattern's values — some left out, for revalidate alone: the gateway's channels of the values given. */
58
70
  to<G extends Function>(gateway: G, params: Readonly<Record<string, string>>): GatewayTarget<G>;
59
71
  }
60
72
  /**
@@ -4,7 +4,7 @@ import { type FakeClient, type RecordedQuery } from 'palgres/testing';
4
4
  import type { CacheClient } from '../cache.js';
5
5
  import type { Schemas, Tables } from '../db-env.js';
6
6
  import type { Token } from '../decorators.js';
7
- import { type Logger } from '../runtime.js';
7
+ import { type GatewaysApi, type Logger } from '../runtime.js';
8
8
  import type { Flags, Notifications, Realtime, Secrets, StorageService } from '../services.js';
9
9
  import { type StringsTable } from '../i18n.js';
10
10
  export type { RecordedQuery };
@@ -18,6 +18,8 @@ export interface RuntimeServices {
18
18
  Notifications: typeof Notifications;
19
19
  Flags: typeof Flags;
20
20
  Realtime: typeof Realtime;
21
+ /** Palbase.gateways: what a service sends a gateway's channel through (to(Gateway, params).emit, .pipe). */
22
+ Gateways: GatewaysApi;
21
23
  }
22
24
  /**
23
25
  * Runs `fn` with these stand-ins: Database is the client given (its typed
@@ -1,7 +1,28 @@
1
- import type { ChatMessage } from './modules/ai.js';
1
+ import type { ChatMessage, MessageAttachment } from './modules/ai.js';
2
2
  export type TurnStatus = 'running' | 'waiting' | 'completed' | 'failed' | 'canceled' | 'timed_out' | 'budget_exceeded';
3
3
  export type WaitingReason = 'approval' | 'tool_output' | 'subagents' | 'message';
4
4
  export type ToolStatus = 'running' | 'awaiting_output' | 'awaiting_approval' | 'approved' | 'denied' | 'expired' | 'completed' | 'failed' | 'canceled';
5
+ /** How a turn ended, in the chat's words — as its event (ai.response.completed / failed) says it. */
6
+ export type TurnEnd = 'completed' | 'failed' | 'stopped' | 'budget_exceeded';
7
+ /**
8
+ * A push, a pull request or a merge the agent asked for (a tool part named push, pull_request or merge), and what a
9
+ * person decides it by: the repository, the branch it writes, the base it targets, the commit it carries.
10
+ */
11
+ export interface Publication {
12
+ /** The engine's word: push_branch, open_pull_request, merge_pull_request. */
13
+ operation: string;
14
+ /** owner/name. */
15
+ repository: string;
16
+ /** The remote as the engine names it. */
17
+ remote: string;
18
+ branch: string;
19
+ /** The branch a pull request targets, or a merge goes into ('' for a push). */
20
+ base: string;
21
+ /** The commit it carries. */
22
+ commit: string;
23
+ /** The engine's own sentence of it. */
24
+ display: string;
25
+ }
5
26
  /** A member's (or the server's) message, as the agent got it. */
6
27
  export interface MessageEvent {
7
28
  type: 'message';
@@ -19,7 +40,16 @@ export interface TurnEvent {
19
40
  /** On running and waiting: the chat has had one member all its life, as it is now (once a second has been added, never again). */
20
41
  solo?: boolean;
21
42
  status: TurnStatus;
22
- /** Why it waits, and where in its answer — and, for a tool call, which: null unless status is waiting. */
43
+ /** A turn the chat's goal opened: the goal, the turn's role (work, or verify: the check of a claimed completion) and its number. */
44
+ goal?: {
45
+ goal_id: string;
46
+ role: 'work' | 'verify' | (string & {});
47
+ seq: number;
48
+ };
49
+ /**
50
+ * Why it waits, and where in its answer — and, for a tool call, which: null unless status is waiting. A publication
51
+ * waiting on a person's yes names its part (tool_call_id, tool_name push|pull_request|merge) and what it does.
52
+ */
23
53
  waiting: {
24
54
  reason: WaitingReason;
25
55
  text_offset: number;
@@ -27,8 +57,17 @@ export interface TurnEvent {
27
57
  tool_call_id?: string;
28
58
  tool_name?: string;
29
59
  input?: unknown;
60
+ expires_at?: string;
61
+ publication?: Publication;
30
62
  } | null;
63
+ /** How it ended, once it has: completed, failed, stopped (a member or the server, or the chat went), budget_exceeded. */
64
+ end?: TurnEnd | (string & {});
65
+ /** Why, when it did not complete and was not stopped — and whether asking again may go otherwise. */
31
66
  error: {
67
+ code: string;
68
+ message: string;
69
+ retryable: boolean;
70
+ } | {
32
71
  code?: string;
33
72
  message?: string;
34
73
  } | string | null;
@@ -47,6 +86,92 @@ export interface ResetEvent {
47
86
  response_id: string;
48
87
  offset: number;
49
88
  }
89
+ /**
90
+ * The model's reasoning as it thinks, when the agent shows it (thinking: visible): a window of a step's at a time,
91
+ * each after the one before — the turn's own, never a subagent's.
92
+ */
93
+ export interface ThinkingEvent {
94
+ type: 'thinking';
95
+ seq: number;
96
+ response_id: string;
97
+ run_id: string;
98
+ /** The model step it is of: its windows go together, its reasoning before what the step writes. */
99
+ step_id?: string;
100
+ text: string;
101
+ /**
102
+ * The model's provider would not give its reasoning (an OpenAI organization not verified for reasoning
103
+ * summaries): said once for the turn, with the model and the provider's reason — and no text.
104
+ */
105
+ unavailable?: true;
106
+ model?: string;
107
+ reason?: string;
108
+ }
109
+ export type GoalKind = 'set' | 'updated' | 'turn_started' | 'judged' | 'verified' | 'status_changed' | 'cleared';
110
+ /** A word of the chat's goal: set, changed, a turn of it begun, a turn judged, a claimed completion checked, its status, cleared. */
111
+ export interface GoalEvent {
112
+ type: 'goal';
113
+ /** The journal's sequence. */
114
+ seq: number;
115
+ goal_id: string;
116
+ kind: GoalKind | (string & {});
117
+ /** set and updated: the goal as it is now. */
118
+ goal?: {
119
+ objective: string;
120
+ rubric: string;
121
+ status: string;
122
+ stop_code: string;
123
+ stop_reason: string;
124
+ phase: string;
125
+ max_turns?: number | null;
126
+ max_cost_usd?: number | null;
127
+ };
128
+ /** updated: what changed. */
129
+ changed?: unknown;
130
+ /** turn_started: the turn's role (work, verify), its number among the goal's turns (the part's seq), its response. */
131
+ role?: string;
132
+ turn?: number;
133
+ response_id?: string;
134
+ run_id?: string;
135
+ /** judged and verified: the evaluator's word, and why; verified: what is missing. */
136
+ verdict?: string;
137
+ reason?: string;
138
+ missing?: unknown;
139
+ /** status_changed: from what to what, and the stop's code (cleared: reason too). */
140
+ from?: string;
141
+ to?: string;
142
+ code?: string;
143
+ }
144
+ /** One message or answer of what a fork began with. */
145
+ export interface HistoryItem {
146
+ /** user: a member's message, or the server's; assistant: the agent's answer. */
147
+ role: 'user' | 'assistant';
148
+ message_id?: string;
149
+ client_id?: string;
150
+ author?: string | null;
151
+ author_name?: string;
152
+ text: string;
153
+ metadata?: Record<string, string>;
154
+ created_at?: string;
155
+ /** An answer's response, in the chat it was forked from. */
156
+ response_id?: string;
157
+ /** Its text here is the first of a longer one: the fork's model read it whole. */
158
+ text_truncated?: boolean;
159
+ /** A message's pictures and files ([] or absent for none), as the chat it was forked from had them. */
160
+ attachments?: MessageAttachment[];
161
+ }
162
+ /** What a fork began with: the chat's messages and its agent's answers before the message it was cut before, a few to a part — written first in the fork's channel. */
163
+ export interface HistoryEvent {
164
+ type: 'history';
165
+ /** The chat it was forked from, and the message it was cut before. */
166
+ forked_from: {
167
+ chat_id: string;
168
+ before_message: string;
169
+ } | null;
170
+ /** This part's place among the history's parts, and how many there are. */
171
+ part: number;
172
+ parts: number;
173
+ items: HistoryItem[];
174
+ }
50
175
  /** A tool call, each time its state changes. */
51
176
  export interface ToolEvent {
52
177
  type: 'tool';
@@ -68,6 +193,16 @@ export interface ToolEvent {
68
193
  is_error?: boolean;
69
194
  /** The subagent's response that made the call, when one did. */
70
195
  agent_response_id?: string | null;
196
+ /** A publication's part (named push, pull_request or merge, its call_id the publication's id): what it does and where. */
197
+ publication?: Publication;
198
+ /** When its approval lapses, while it waits for one. */
199
+ expires_at?: string;
200
+ /**
201
+ * Its answer is shown by its end (output: its last 16 KiB): how many bytes before it were left out, and that the
202
+ * part was cut to fit the channel — the whole answer is read by its call (chatToolCall).
203
+ */
204
+ omittedBytes: number;
205
+ outputTruncated: boolean;
71
206
  [field: string]: unknown;
72
207
  }
73
208
  /** A subagent at work — or one the agent asked for and was refused (status denied, with its reason). */
@@ -88,7 +223,37 @@ export interface AgentEvent {
88
223
  reason?: string;
89
224
  [field: string]: unknown;
90
225
  }
91
- export type ChatEvent = MessageEvent | TurnEvent | TextEvent | ResetEvent | ToolEvent | AgentEvent;
226
+ /** The chat's live voice began, or ended: whose it is, what becomes of what is asked of it, how long it lasted, why it ended. */
227
+ export interface VoiceEvent {
228
+ type: 'voice';
229
+ /** The journal's sequence of its start; 0 for its end, told from the module's record under the voice's id. */
230
+ seq: number;
231
+ voice_id: string;
232
+ /** Its member; null: the server's own. */
233
+ user_id: string | null;
234
+ /** chat: what the person asks for is a turn of the chat; events: the backend's to do (ai.voice.delegated). */
235
+ delegation: 'chat' | 'events' | (string & {});
236
+ status: 'started' | 'closed' | (string & {});
237
+ /** On closed: how long it lasted, and why it ended (close_requested, member_removed, chat_deleted, replaced, lost, …). */
238
+ seconds?: number;
239
+ close_reason?: string;
240
+ }
241
+ /** A line said in the chat's voice — the person's (user) or the voice's (assistant): a message of kind voice of its history. */
242
+ export interface TranscriptEvent {
243
+ type: 'transcript';
244
+ seq: number;
245
+ /** The line's id: its message's in the chat's history. */
246
+ id: string;
247
+ voice_id: string;
248
+ role: 'user' | 'assistant' | (string & {});
249
+ /** The member who said it; null: the voice. */
250
+ user_id: string | null;
251
+ author_name: string;
252
+ text: string;
253
+ final: boolean;
254
+ created_at: string;
255
+ }
256
+ export type ChatEvent = MessageEvent | TurnEvent | TextEvent | ResetEvent | ThinkingEvent | ToolEvent | AgentEvent | GoalEvent | HistoryEvent | VoiceEvent | TranscriptEvent;
92
257
  /** A chat refusal of the module's, said in words its caller can act on; anything else (or one said already) as it was. */
93
258
  export declare function chatRefusal(e: unknown): unknown;
94
259
  /** The same promise, its chat refusals said as chatRefusal says them. */