@alisio/core 0.1.0-alpha.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +52 -7
  2. package/dist/agents/active.d.ts +99 -0
  3. package/dist/agents/active.js +218 -0
  4. package/dist/agents/capabilities.d.ts +26 -0
  5. package/dist/agents/capabilities.js +120 -0
  6. package/dist/agents/create-agent-skill.d.ts +11 -0
  7. package/dist/agents/create-agent-skill.js +60 -0
  8. package/dist/agents/definitions.d.ts +88 -0
  9. package/dist/agents/definitions.js +404 -0
  10. package/dist/agents/draft.d.ts +41 -0
  11. package/dist/agents/draft.js +133 -0
  12. package/dist/agents/service.d.ts +57 -0
  13. package/dist/agents/service.js +86 -0
  14. package/dist/agents/templates.d.ts +8 -0
  15. package/dist/agents/templates.js +102 -0
  16. package/dist/analysis/capabilities.d.ts +48 -0
  17. package/dist/analysis/capabilities.js +90 -0
  18. package/dist/analysis/data/csv.d.ts +46 -0
  19. package/dist/analysis/data/csv.js +240 -0
  20. package/dist/analysis/data/datasets.d.ts +132 -0
  21. package/dist/analysis/data/datasets.js +441 -0
  22. package/dist/analysis/data/describe.d.ts +27 -0
  23. package/dist/analysis/data/describe.js +103 -0
  24. package/dist/analysis/data/engine-client.d.ts +58 -0
  25. package/dist/analysis/data/engine-client.js +304 -0
  26. package/dist/analysis/data/engine-entry.d.ts +1 -0
  27. package/dist/analysis/data/engine-entry.js +59 -0
  28. package/dist/analysis/data/engine-source.d.ts +4 -0
  29. package/dist/analysis/data/engine-source.js +6 -0
  30. package/dist/analysis/data/engine-types.d.ts +86 -0
  31. package/dist/analysis/data/engine-types.js +7 -0
  32. package/dist/analysis/data/infer.d.ts +42 -0
  33. package/dist/analysis/data/infer.js +118 -0
  34. package/dist/analysis/data/ingest-worker.d.ts +17 -0
  35. package/dist/analysis/data/ingest-worker.js +365 -0
  36. package/dist/analysis/data/json.d.ts +19 -0
  37. package/dist/analysis/data/json.js +44 -0
  38. package/dist/analysis/data/query-worker.d.ts +12 -0
  39. package/dist/analysis/data/query-worker.js +75 -0
  40. package/dist/analysis/data/rows.d.ts +39 -0
  41. package/dist/analysis/data/rows.js +120 -0
  42. package/dist/analysis/data/sql-guard.d.ts +20 -0
  43. package/dist/analysis/data/sql-guard.js +129 -0
  44. package/dist/analysis/data/stats.d.ts +18 -0
  45. package/dist/analysis/data/stats.js +77 -0
  46. package/dist/analysis/install-hints.d.ts +33 -0
  47. package/dist/analysis/install-hints.js +144 -0
  48. package/dist/analysis/janitor.d.ts +60 -0
  49. package/dist/analysis/janitor.js +264 -0
  50. package/dist/analysis/jobs.d.ts +94 -0
  51. package/dist/analysis/jobs.js +165 -0
  52. package/dist/analysis/oci.d.ts +129 -0
  53. package/dist/analysis/oci.js +330 -0
  54. package/dist/analysis/python/sources.d.ts +2 -0
  55. package/dist/analysis/python/sources.js +10 -0
  56. package/dist/analysis/requirements.d.ts +32 -0
  57. package/dist/analysis/requirements.js +73 -0
  58. package/dist/analysis/rerun.d.ts +40 -0
  59. package/dist/analysis/rerun.js +136 -0
  60. package/dist/analysis/runtime-manager.d.ts +146 -0
  61. package/dist/analysis/runtime-manager.js +472 -0
  62. package/dist/analysis/status.d.ts +21 -0
  63. package/dist/analysis/status.js +52 -0
  64. package/dist/application.d.ts +118 -3
  65. package/dist/application.js +510 -19
  66. package/dist/artifacts/kinds.d.ts +35 -0
  67. package/dist/artifacts/kinds.js +177 -0
  68. package/dist/artifacts/publisher.d.ts +7 -0
  69. package/dist/artifacts/publisher.js +29 -0
  70. package/dist/artifacts/store.d.ts +119 -0
  71. package/dist/artifacts/store.js +581 -0
  72. package/dist/background/janitor.d.ts +22 -0
  73. package/dist/background/janitor.js +62 -0
  74. package/dist/background/mirror.d.ts +14 -0
  75. package/dist/background/mirror.js +41 -0
  76. package/dist/background/notify.d.ts +58 -0
  77. package/dist/background/notify.js +172 -0
  78. package/dist/background/output.d.ts +46 -0
  79. package/dist/background/output.js +179 -0
  80. package/dist/background/service.d.ts +137 -0
  81. package/dist/background/service.js +410 -0
  82. package/dist/background/store.d.ts +103 -0
  83. package/dist/background/store.js +195 -0
  84. package/dist/changelog/data.d.ts +6 -0
  85. package/dist/changelog/data.js +293 -0
  86. package/dist/changelog/index.d.ts +5 -0
  87. package/dist/changelog/index.js +7 -0
  88. package/dist/changelog/parse.d.ts +53 -0
  89. package/dist/changelog/parse.js +160 -0
  90. package/dist/commands/builtins.d.ts +10 -0
  91. package/dist/commands/builtins.js +161 -0
  92. package/dist/commands/catalog.d.ts +86 -0
  93. package/dist/commands/catalog.js +304 -0
  94. package/dist/config.d.ts +252 -5
  95. package/dist/config.js +514 -13
  96. package/dist/core/compaction.js +13 -2
  97. package/dist/core/contracts.d.ts +180 -3
  98. package/dist/core/opt-in.d.ts +12 -0
  99. package/dist/core/opt-in.js +12 -0
  100. package/dist/core/output-limit.d.ts +34 -0
  101. package/dist/core/output-limit.js +39 -0
  102. package/dist/core/run-stats.d.ts +16 -0
  103. package/dist/core/run-stats.js +8 -0
  104. package/dist/core/runner.d.ts +152 -4
  105. package/dist/core/runner.js +792 -138
  106. package/dist/core/timeout.d.ts +15 -0
  107. package/dist/core/timeout.js +61 -0
  108. package/dist/core/truncation.d.ts +30 -0
  109. package/dist/core/truncation.js +83 -0
  110. package/dist/goal/blockers.d.ts +45 -0
  111. package/dist/goal/blockers.js +51 -0
  112. package/dist/goal/budget.d.ts +24 -0
  113. package/dist/goal/budget.js +53 -0
  114. package/dist/goal/command.d.ts +36 -0
  115. package/dist/goal/command.js +98 -0
  116. package/dist/goal/format.d.ts +19 -0
  117. package/dist/goal/format.js +102 -0
  118. package/dist/goal/host.d.ts +29 -0
  119. package/dist/goal/host.js +42 -0
  120. package/dist/goal/index.d.ts +9 -0
  121. package/dist/goal/index.js +9 -0
  122. package/dist/goal/machine.d.ts +186 -0
  123. package/dist/goal/machine.js +344 -0
  124. package/dist/goal/prompts.d.ts +10 -0
  125. package/dist/goal/prompts.js +75 -0
  126. package/dist/goal/service.d.ts +113 -0
  127. package/dist/goal/service.js +273 -0
  128. package/dist/goal/store.d.ts +59 -0
  129. package/dist/goal/store.js +184 -0
  130. package/dist/index.d.ts +51 -6
  131. package/dist/index.js +49 -4
  132. package/dist/mcp/connector.js +38 -6
  133. package/dist/mcp/rich.d.ts +41 -0
  134. package/dist/mcp/rich.js +362 -0
  135. package/dist/permissions/modes.d.ts +72 -0
  136. package/dist/permissions/modes.js +98 -0
  137. package/dist/plan/exit-plan.d.ts +25 -0
  138. package/dist/plan/exit-plan.js +130 -0
  139. package/dist/plan/state.d.ts +102 -0
  140. package/dist/plan/state.js +165 -0
  141. package/dist/plugins/host.d.ts +39 -1
  142. package/dist/plugins/host.js +150 -9
  143. package/dist/providers/settings.d.ts +15 -0
  144. package/dist/providers/settings.js +38 -0
  145. package/dist/reload.d.ts +75 -0
  146. package/dist/reload.js +204 -0
  147. package/dist/resources/context.d.ts +5 -0
  148. package/dist/resources/context.js +13 -1
  149. package/dist/runtime/access.d.ts +62 -0
  150. package/dist/runtime/access.js +115 -0
  151. package/dist/runtime/blobs.d.ts +51 -0
  152. package/dist/runtime/blobs.js +177 -0
  153. package/dist/runtime/diff.d.ts +23 -0
  154. package/dist/runtime/diff.js +154 -0
  155. package/dist/runtime/ids.d.ts +8 -0
  156. package/dist/runtime/ids.js +32 -0
  157. package/dist/runtime/paths.d.ts +11 -0
  158. package/dist/runtime/paths.js +17 -1
  159. package/dist/runtime/process.d.ts +22 -0
  160. package/dist/runtime/process.js +100 -15
  161. package/dist/runtime/sqlite.d.ts +2 -0
  162. package/dist/runtime/sqlite.js +18 -0
  163. package/dist/runtime/store.d.ts +91 -3
  164. package/dist/runtime/store.js +465 -11
  165. package/dist/runtime/zip.d.ts +24 -0
  166. package/dist/runtime/zip.js +126 -0
  167. package/dist/sessions/children.js +17 -1
  168. package/dist/sessions/side-questions.d.ts +57 -0
  169. package/dist/sessions/side-questions.js +161 -0
  170. package/dist/startup/screen.js +6 -5
  171. package/dist/tools/analysis.d.ts +37 -0
  172. package/dist/tools/analysis.js +573 -0
  173. package/dist/tools/artifacts.d.ts +14 -0
  174. package/dist/tools/artifacts.js +143 -0
  175. package/dist/tools/background.d.ts +7 -0
  176. package/dist/tools/background.js +165 -0
  177. package/dist/tools/data.d.ts +7 -0
  178. package/dist/tools/data.js +160 -0
  179. package/dist/tools/execute.d.ts +5 -0
  180. package/dist/tools/execute.js +15 -3
  181. package/dist/tools/goal.d.ts +19 -0
  182. package/dist/tools/goal.js +126 -0
  183. package/dist/tools/search.d.ts +17 -3
  184. package/dist/tools/search.js +153 -0
  185. package/dist/tools/standard.d.ts +4 -1
  186. package/dist/tools/standard.js +99 -18
  187. package/dist/version.d.ts +5 -3
  188. package/dist/version.js +35 -6
  189. package/package.json +2 -2
@@ -13,6 +13,14 @@ export declare function isMissingCommand(error: unknown): boolean;
13
13
  export declare const RIPGREP_INSTALL_HINT: string;
14
14
  /** Human-readable detection status for `alisio doctor`. */
15
15
  export declare function ripgrepDiagnostic(found: boolean): string | undefined;
16
+ /** Whether a process id belongs to a live process (EPERM still means alive). */
17
+ export declare function isProcessAlive(pid: number): boolean;
18
+ /**
19
+ * Kills a spawned process and everything it started: the process group on POSIX (children are
20
+ * spawned with `detached`, so the pid leads its own group) and `taskkill /T /F` on Windows.
21
+ * Synchronous, so it is also safe inside a `process.on("exit")` handler. Never throws.
22
+ */
23
+ export declare function killProcessTree(pid: number, fallback?: () => void): void;
16
24
  export declare function runProcess(command: string, args: string[], options: {
17
25
  cwd: string;
18
26
  signal: AbortSignal;
@@ -22,4 +30,18 @@ export declare function runProcess(command: string, args: string[], options: {
22
30
  onData?: (chunk: string) => void;
23
31
  /** On cancellation: SIGTERM the process group, then SIGKILL after this grace (default 5s). */
24
32
  killGraceMs?: number;
33
+ /**
34
+ * What happens when a stream exceeds `maxBytes`: `kill` (default) stops the process; `truncate`
35
+ * lets it run and keeps only the LAST `maxBytes` of each stream (the result is `truncated`).
36
+ */
37
+ onOverflow?: "kill" | "truncate";
38
+ /** Full stdout/stderr copies (up to `maxLogBytes` each), independent of `maxBytes`. */
39
+ logFiles?: {
40
+ stdout: string;
41
+ stderr: string;
42
+ };
43
+ /** Cap of each log file (default 10 MiB); a marker line notes the cut. */
44
+ maxLogBytes?: number;
45
+ /** Called once with the pid of the spawned process (it leads its own group on POSIX). */
46
+ onSpawn?: (pid: number) => void;
25
47
  }): Promise<ProcessResult>;
@@ -1,4 +1,5 @@
1
1
  import { spawn, spawnSync } from "node:child_process";
2
+ import { createWriteStream } from "node:fs";
2
3
  /**
3
4
  * Detect a spawn failure caused by a missing executable (`ENOENT`), so tools can explain how to
4
5
  * install the dependency instead of surfacing the raw OS error.
@@ -17,6 +18,37 @@ export const RIPGREP_INSTALL_HINT = "Install ripgrep (rg): `sudo apt install rip
17
18
  export function ripgrepDiagnostic(found) {
18
19
  return found ? undefined : RIPGREP_INSTALL_HINT;
19
20
  }
21
+ /** Whether a process id belongs to a live process (EPERM still means alive). */
22
+ export function isProcessAlive(pid) {
23
+ try {
24
+ process.kill(pid, 0);
25
+ return true;
26
+ }
27
+ catch (e) {
28
+ return e.code !== "ESRCH";
29
+ }
30
+ }
31
+ /**
32
+ * Kills a spawned process and everything it started: the process group on POSIX (children are
33
+ * spawned with `detached`, so the pid leads its own group) and `taskkill /T /F` on Windows.
34
+ * Synchronous, so it is also safe inside a `process.on("exit")` handler. Never throws.
35
+ */
36
+ export function killProcessTree(pid, fallback) {
37
+ try {
38
+ if (process.platform !== "win32")
39
+ process.kill(-pid, "SIGKILL");
40
+ else
41
+ spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore" });
42
+ }
43
+ catch {
44
+ try {
45
+ fallback?.();
46
+ }
47
+ catch {
48
+ /* already gone */
49
+ }
50
+ }
51
+ }
20
52
  export async function runProcess(command, args, options) {
21
53
  options.signal.throwIfAborted();
22
54
  const signal = AbortSignal.any([
@@ -31,6 +63,8 @@ export async function runProcess(command, args, options) {
31
63
  detached: process.platform !== "win32",
32
64
  windowsHide: true,
33
65
  });
66
+ if (child.pid !== undefined)
67
+ options.onSpawn?.(child.pid);
34
68
  const exited = new Promise((resolveExit, reject) => {
35
69
  child.once("error", reject);
36
70
  child.once("close", (code, sig) => resolveExit(code ?? (sig ? 128 : 1)));
@@ -59,36 +93,86 @@ export async function runProcess(command, args, options) {
59
93
  const kill = () => {
60
94
  if (finished || child.pid === undefined)
61
95
  return;
62
- try {
63
- if (process.platform !== "win32")
64
- process.kill(-child.pid, "SIGKILL");
65
- else {
66
- spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" });
67
- child.kill();
68
- }
69
- }
70
- catch {
71
- child.kill("SIGKILL");
72
- }
96
+ killProcessTree(child.pid, () => child.kill("SIGKILL"));
97
+ if (process.platform === "win32")
98
+ child.kill();
73
99
  };
74
100
  signal.addEventListener("abort", terminate, { once: true });
75
101
  if (signal.aborted)
76
102
  terminate();
77
103
  let size = 0, truncated = false;
78
104
  const max = options.maxBytes ?? 32_000;
79
- const consume = (stream) => new Promise((resolveText, reject) => {
105
+ const tailMode = options.onOverflow === "truncate";
106
+ const maxLog = options.maxLogBytes ?? 10 * 1024 * 1024;
107
+ const logs = [];
108
+ const logger = (path) => {
109
+ if (!path)
110
+ return undefined;
111
+ const out = createWriteStream(path, { flags: "w" });
112
+ out.on("error", () => {
113
+ /* A log write failure never fails the process run. */
114
+ });
115
+ logs.push(out);
116
+ let written = 0, cut = false;
117
+ return (chunk) => {
118
+ if (cut)
119
+ return;
120
+ const room = maxLog - written;
121
+ if (chunk.byteLength > room) {
122
+ out.write(chunk.subarray(0, Math.max(0, room)));
123
+ out.write(Buffer.from("\n[log truncated]\n"));
124
+ cut = true;
125
+ written = maxLog;
126
+ return;
127
+ }
128
+ out.write(chunk);
129
+ written += chunk.byteLength;
130
+ };
131
+ };
132
+ const consume = (stream, logPath) => new Promise((resolveText, reject) => {
80
133
  const decoder = new TextDecoder();
134
+ const log = logger(logPath);
81
135
  let text = "", done = false;
136
+ // Tail mode keeps raw buffers and decodes once at the end (a cut may split a character).
137
+ const tail = [];
138
+ let tailBytes = 0;
82
139
  const finish = () => {
83
140
  if (done)
84
141
  return;
85
142
  done = true;
86
- text += decoder.decode();
143
+ if (tailMode) {
144
+ const joined = Buffer.concat(tail);
145
+ text = new TextDecoder().decode(joined).replace(/^\uFFFD+/, "");
146
+ }
147
+ else
148
+ text += decoder.decode();
87
149
  resolveText(text);
88
150
  };
89
151
  stream.on("data", (value) => {
90
152
  if (done)
91
153
  return;
154
+ log?.(value);
155
+ if (tailMode) {
156
+ const live = decoder.decode(value, { stream: true });
157
+ if (live)
158
+ options.onData?.(live);
159
+ tail.push(value);
160
+ tailBytes += value.byteLength;
161
+ while (tailBytes > max && tail.length) {
162
+ const first = tail[0];
163
+ const excess = tailBytes - max;
164
+ if (first.byteLength <= excess) {
165
+ tail.shift();
166
+ tailBytes -= first.byteLength;
167
+ }
168
+ else {
169
+ tail[0] = first.subarray(excess);
170
+ tailBytes -= excess;
171
+ }
172
+ truncated = true;
173
+ }
174
+ return;
175
+ }
92
176
  const remaining = Math.max(0, max - size), slice = value.subarray(0, remaining);
93
177
  size += slice.byteLength;
94
178
  const chunk = decoder.decode(slice, { stream: true });
@@ -107,8 +191,8 @@ export async function runProcess(command, args, options) {
107
191
  });
108
192
  try {
109
193
  const [stdout, stderr, exitCode] = await Promise.all([
110
- consume(child.stdout),
111
- consume(child.stderr),
194
+ consume(child.stdout, options.logFiles?.stdout),
195
+ consume(child.stderr, options.logFiles?.stderr),
112
196
  exited,
113
197
  ]);
114
198
  signal.throwIfAborted();
@@ -118,5 +202,6 @@ export async function runProcess(command, args, options) {
118
202
  signal.removeEventListener("abort", terminate);
119
203
  clearTimeout(graceTimer);
120
204
  kill();
205
+ await Promise.all(logs.map((out) => new Promise((resolveLog) => out.end(() => resolveLog()))));
121
206
  }
122
207
  }
@@ -1,3 +1,5 @@
1
1
  import type { SqlDatabase } from "@alisio/sdk";
2
2
  export declare function isSqliteExperimentalWarning(message: string, type?: string): boolean;
3
+ /** Opens an existing database file read-only (no directory or file is created). */
4
+ export declare function openReadOnlyDatabase(path: string): SqlDatabase;
3
5
  export declare function openDatabase(path: string): SqlDatabase;
@@ -34,6 +34,24 @@ function sqlite() {
34
34
  return module;
35
35
  }
36
36
  const plain = (row) => row === undefined || row === null ? undefined : { ...row };
37
+ /** Opens an existing database file read-only (no directory or file is created). */
38
+ export function openReadOnlyDatabase(path) {
39
+ const db = new (sqlite().DatabaseSync)(path, { readOnly: true });
40
+ db.exec("PRAGMA query_only=ON;");
41
+ return {
42
+ exec: (sql) => db.exec(sql),
43
+ prepare(sql) {
44
+ const raw = db.prepare(sql);
45
+ return {
46
+ run: (...params) => raw.run(...params),
47
+ get: (...params) => plain(raw.get(...params)),
48
+ all: (...params) => raw.all(...params).map((r) => ({ ...r })),
49
+ };
50
+ },
51
+ transaction: (fn) => fn(),
52
+ close: () => db.close(),
53
+ };
54
+ }
37
55
  export function openDatabase(path) {
38
56
  const memory = path === ":memory:";
39
57
  const fresh = !memory && !existsSync(path);
@@ -1,10 +1,15 @@
1
1
  import { type Message, type SqlDatabase, type ToolCall, type ToolResult } from "@alisio/sdk";
2
- import type { Session, SessionStore } from "../core/contracts.ts";
2
+ import type { BeginRunInput, EndRunInput, EventPage, MessagePage, PageOptions, RunRecord, Session, SessionStore, ToolCallMeta } from "../core/contracts.ts";
3
3
  export declare class SQLiteStore implements SessionStore {
4
4
  readonly db: SqlDatabase;
5
5
  constructor(path: string);
6
6
  /** Forward-only, idempotent migrations. Existing databases keep all rows. */
7
7
  private migrate;
8
+ /**
9
+ * The root of a session (itself for roots), following `parent_id`. Unknown ids are their own
10
+ * root, so callers never throw on a session another process has not written yet.
11
+ */
12
+ rootOf(id: string): string;
8
13
  create(workspace: string, provider: string, model: string): Session;
9
14
  private row;
10
15
  get(id: string): Session;
@@ -23,15 +28,98 @@ export declare class SQLiteStore implements SessionStore {
23
28
  interruptStale(): number;
24
29
  messages(id: string): Message[];
25
30
  append(id: string, message: Message): void;
31
+ private touch;
26
32
  setModel(id: string, model: string): void;
27
33
  compact(id: string, replaced: number, summary: Message): void;
28
34
  overwrite(id: string, messages: Message[]): void;
29
35
  acquire(id: string): void;
30
36
  release(id: string): void;
31
- beginCall(id: string, call: ToolCall): void;
37
+ beginCall(id: string, call: ToolCall, meta?: ToolCallMeta): void;
32
38
  endCall(id: string, call: ToolCall, result: ToolResult): void;
39
+ /**
40
+ * Calls of a session with one effect (for example `write`), oldest first. Needs the v4
41
+ * `effect`/`run_id`/`started_at` columns; rows written by older versions have no effect.
42
+ */
43
+ effectCalls(session: string, effect: string): Array<{
44
+ callId: string;
45
+ name: string;
46
+ runId?: string;
47
+ startedAt?: number;
48
+ }>;
49
+ /** Rich (ui/image) parts persist as part of the serialized result and replay verbatim. */
50
+ callResult(id: string, callId: string): ToolResult | undefined;
33
51
  reconcile(id: string, acknowledge?: boolean): void;
34
- event(id: string, runId: string, type: string, data: unknown): void;
52
+ /** Persists a durable event and returns its global `events.seq`. */
53
+ event(id: string, runId: string, type: string, data: unknown, meta?: {
54
+ correlationId?: string;
55
+ }): number;
56
+ private runRow;
57
+ private runById;
58
+ beginRun(input: BeginRunInput): {
59
+ run: RunRecord;
60
+ created: boolean;
61
+ };
62
+ endRun(id: string, input: EndRunInput): void;
63
+ runByRequest(session: string, requestId: string): RunRecord | undefined;
64
+ runs(session: string, options?: {
65
+ limit?: number;
66
+ }): RunRecord[];
67
+ interruptRuns(): number;
68
+ /**
69
+ * A page of messages by `seq`. With `after`, the first `limit` rows after it; otherwise the
70
+ * newest `limit` rows (before `before` when given). Items are always in ascending order.
71
+ */
72
+ messagesPage(session: string, options?: PageOptions & {
73
+ compacted?: boolean;
74
+ }): MessagePage;
75
+ /** Durable events of a session after `after` (a previous `eventId`), ascending. */
76
+ eventsPage(session: string, options?: PageOptions): EventPage;
77
+ /**
78
+ * Known workspaces for web clients: the workspaces of root sessions plus the ones recorded with
79
+ * `recordWorkspace` (v4 `workspaces` table), with their UI metadata (v5: `archivedAt`) and
80
+ * root-session count.
81
+ */
82
+ workspaces(): Array<{
83
+ path: string;
84
+ label?: string;
85
+ pinned: boolean;
86
+ lastOpenedAt?: number;
87
+ archivedAt?: number;
88
+ sessions: number;
89
+ }>;
90
+ /**
91
+ * Records (upserts) a workspace and patches its UI metadata (`label: null` clears the label;
92
+ * `archived` stamps or clears `archived_at` and never touches its sessions).
93
+ */
94
+ recordWorkspace(path: string, patch?: {
95
+ label?: string | null;
96
+ pinned?: boolean;
97
+ lastOpenedAt?: number;
98
+ archived?: boolean;
99
+ }): void;
100
+ /**
101
+ * Patches web-facing session metadata: title, pin, archive flag and `options` (shallow-merged
102
+ * into the stored options object). Does not touch the run-relevant columns.
103
+ */
104
+ updateSessionMeta(id: string, patch: {
105
+ title?: string | null;
106
+ pinned?: boolean;
107
+ archived?: boolean;
108
+ options?: Record<string, unknown>;
109
+ }): void;
110
+ /**
111
+ * Atomic read-modify-write of `sessions.options` (`BEGIN IMMEDIATE`: safe across processes that
112
+ * share the database). `change` receives the current options and returns the keys to merge
113
+ * (`undefined` removes a key; `undefined` as a whole changes nothing) and a value for the caller.
114
+ */
115
+ mutateSessionOptions<T>(id: string, change: (current: Record<string, unknown>) => {
116
+ patch?: Record<string, unknown>;
117
+ value: T;
118
+ }): T;
119
+ /** `MAX(events.seq)` of a session (0 without events): the web snapshot cursor. */
120
+ lastEventId(session: string): number;
121
+ /** The pid of another live process holding the session lock, if any. */
122
+ lockedBy(id: string): number | undefined;
35
123
  getState(plugin: string, key: string): unknown;
36
124
  setState(plugin: string, key: string, value: unknown): void;
37
125
  close(): void;