@sigrun/shared 0.0.0-stage → 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.
package/dist/jobs.d.ts ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * §8.2 job wire types — THE jobs channel contract.
3
+ *
4
+ * Part of the shared leaf package (`@sigrun/shared`): the server owns
5
+ * the runtime half (src/jobs/* — validation, schedule math) and both
6
+ * clients (webui, tui) import these types directly. Like every wire
7
+ * module, imports are TYPE-ONLY from sibling shared modules (the
8
+ * views-purity guard enforces this), so the module erases at compile
9
+ * time and contributes no runtime.
10
+ *
11
+ * The shapes follow spec §8.2's schedule/action grammar verbatim:
12
+ * a schedule is exactly one of a 5-field `cron` (with an optional IANA
13
+ * `timezone`, defaulting to the server timezone), an `every` interval
14
+ * (`^\\d+(s|m|h|d)$`, minimum 60 s), or a one-shot ISO 8601 `at`; an
15
+ * action is a `prompt` (optionally pinning the model/thinking of a NEW
16
+ * conversation, or targeting a fixed conversation id) or a `task`
17
+ * (a registered extension task name plus optional JSON input).
18
+ */
19
+ import type { Sharing } from "./access.js";
20
+ import type { JsonValue } from "./json.js";
21
+ export type { JsonValue };
22
+ /** When a job fires: a cron (5-field, TZ-aware), an interval, or one instant. */
23
+ export type JobSchedule = {
24
+ cron: string;
25
+ timezone?: string;
26
+ } | {
27
+ every: string;
28
+ } | {
29
+ at: string;
30
+ };
31
+ /** What a job does when it fires (spec §8.2). */
32
+ export type JobAction = {
33
+ kind: "prompt";
34
+ prompt: string;
35
+ model?: string;
36
+ thinking?: string;
37
+ conversation: "new" | number;
38
+ runSharing?: Sharing;
39
+ } | {
40
+ kind: "task";
41
+ task: string;
42
+ input?: JsonValue;
43
+ };
44
+ /** A job's declared shape — what every source (api/file/extension) supplies. */
45
+ export type JobSpec = {
46
+ name: string;
47
+ schedule: JobSchedule;
48
+ action: JobAction;
49
+ overlap?: "skip" | "queue";
50
+ catchUp?: "once" | "none";
51
+ enabled?: boolean;
52
+ owner?: string;
53
+ sharing?: Sharing;
54
+ };
55
+ /** One job as `GET /v1/jobs` serves it (the list row and the detail). */
56
+ export type JobWire = {
57
+ id: string;
58
+ name: string;
59
+ source: "api" | "file" | "extension";
60
+ owner?: string;
61
+ sharing?: Sharing;
62
+ schedule: JobSchedule;
63
+ action: JobAction;
64
+ enabled: boolean;
65
+ state: "waiting" | "firing" | "disabled" | "blocked";
66
+ reason?: string;
67
+ version: number;
68
+ nextRun?: string;
69
+ lastRun?: JobRunWire;
70
+ lastError?: string;
71
+ access: "read" | "write";
72
+ };
73
+ /** One fire of a job, as the job's `lastRun` copy serves it. */
74
+ export type JobRunWire = {
75
+ jobId: string;
76
+ version: number;
77
+ fireAt: string;
78
+ manual: boolean;
79
+ conversationId?: number;
80
+ taskId?: number;
81
+ outcome: "running" | "succeeded" | "failed" | "skipped";
82
+ startedAt: string;
83
+ settledAt?: string;
84
+ error?: string;
85
+ };
86
+ /**
87
+ * One immutable revision of a job (`sigrun.job.revision`, keyed
88
+ * `<jobId>:<version>`): the spec exactly as it stood at that version.
89
+ */
90
+ export type JobRevisionWire = {
91
+ jobId: string;
92
+ version: number;
93
+ source: "api" | "file" | "extension";
94
+ spec: JobSpec;
95
+ owner?: string;
96
+ sharing?: Sharing;
97
+ enabled: boolean;
98
+ changedAt: string;
99
+ changedBy: string;
100
+ };
package/dist/jobs.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * §8.2 job wire types — THE jobs channel contract.
3
+ *
4
+ * Part of the shared leaf package (`@sigrun/shared`): the server owns
5
+ * the runtime half (src/jobs/* — validation, schedule math) and both
6
+ * clients (webui, tui) import these types directly. Like every wire
7
+ * module, imports are TYPE-ONLY from sibling shared modules (the
8
+ * views-purity guard enforces this), so the module erases at compile
9
+ * time and contributes no runtime.
10
+ *
11
+ * The shapes follow spec §8.2's schedule/action grammar verbatim:
12
+ * a schedule is exactly one of a 5-field `cron` (with an optional IANA
13
+ * `timezone`, defaulting to the server timezone), an `every` interval
14
+ * (`^\\d+(s|m|h|d)$`, minimum 60 s), or a one-shot ISO 8601 `at`; an
15
+ * action is a `prompt` (optionally pinning the model/thinking of a NEW
16
+ * conversation, or targeting a fixed conversation id) or a `task`
17
+ * (a registered extension task name plus optional JSON input).
18
+ */
19
+ export {};
package/dist/json.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The JSON value mirrors (the shared leaf's local declarations of
3
+ * chord/pi-durable's `JsonValue`/`JsonObject`).
4
+ *
5
+ * Declared LOCALLY — never imported — so every shared module stays
6
+ * import-free at runtime (the shared-package invariant: the webui
7
+ * compiles DOM-only against these files, and a dependency import would
8
+ * drag a module only one client can resolve). The shapes mirror
9
+ * `@earendil-works/chord`'s `JsonValue` exactly (structural identity —
10
+ * the server-side doc definitions accept records built from these types
11
+ * wherever pi-durable's own `JsonObject` is required).
12
+ */
13
+ /** A strict-JSON value: primitives, arrays, or a JSON object. */
14
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
15
+ [key: string]: JsonValue;
16
+ };
17
+ /** JSON object used as the root of every durable document. */
18
+ export type JsonObject = {
19
+ [key: string]: JsonValue;
20
+ };
package/dist/json.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The JSON value mirrors (the shared leaf's local declarations of
3
+ * chord/pi-durable's `JsonValue`/`JsonObject`).
4
+ *
5
+ * Declared LOCALLY — never imported — so every shared module stays
6
+ * import-free at runtime (the shared-package invariant: the webui
7
+ * compiles DOM-only against these files, and a dependency import would
8
+ * drag a module only one client can resolve). The shapes mirror
9
+ * `@earendil-works/chord`'s `JsonValue` exactly (structural identity —
10
+ * the server-side doc definitions accept records built from these types
11
+ * wherever pi-durable's own `JsonObject` is required).
12
+ */
13
+ export {};
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The session-meta WIRE types (spec §5) — the client-facing half of
3
+ * `src/server/session-meta.ts` (which owns the document/patch runtime).
4
+ *
5
+ * This module is part of the shared leaf package (`@sigrun/shared`): the
6
+ * tui and webui import these shapes from here, never from server source
7
+ * (the load-bearing decoupling — the tui is a pure client of the wire).
8
+ *
9
+ * PURE module: no imports at all (the views-purity guard enforces this
10
+ * across `shared/src`) — the `import type` below is the sanctioned wire-
11
+ * module exception: sibling-only (`./access.js`), type-only.
12
+ */
13
+ import type { Sharing } from "./access.js";
14
+ export type { Sharing } from "./access.js";
15
+ /**
16
+ * Doc header (from the server module — the runtime semantics live there):
17
+ *
18
+ * One latest-only singleton doc per conversation (kind
19
+ * `"sigrun.session-meta"`): pin/tag/archive flags, the composer draft
20
+ * (survives reloads — `draft.attachments` are workspace-relative paths
21
+ * from the uploads endpoint), the session source, the user-facing session
22
+ * name (T5d), the multi-user `owner` seam, and the §5.9 `sharing` state.
23
+ * `owner` and `source` are SERVER-ONLY: the auth hook stamps the owner at
24
+ * create/fork/import, and a client PATCH carrying either key is a 400
25
+ * `invalid_field`.
26
+ *
27
+ * Server-owned fields (`autoName`, `autoNameAttempts`,
28
+ * `lastActivityAt`) are written by host code only (activity.ts, the A2
29
+ * auto-namer); they are NOT patch surface — the strict validator keeps
30
+ * rejecting them as `invalid_field`.
31
+ */
32
+ /** The per-conversation session metadata (spec §5). */
33
+ export type SessionMeta = {
34
+ /** Sidebar pin (pinned conversations float to the top). */
35
+ pinned: boolean;
36
+ /** Sidebar tags; a PATCH replaces the array wholesale. */
37
+ tagged: string[];
38
+ /** Archived/hidden from the default sidebar listing. */
39
+ archived: boolean;
40
+ /** The composer draft; survives a reload. */
41
+ draft: {
42
+ text: string;
43
+ attachments: string[];
44
+ };
45
+ /** Who created the session: an interactive user, a cron job, a channel
46
+ * adapter — a free-form server-owned label (spec §5.9: no longer a
47
+ * closed enum; never patchable). */
48
+ source: string;
49
+ /**
50
+ * Multi-user seam (spec §5.9): the caller identity the auth hook
51
+ * stamped at create/fork/import, read by the listing route and the
52
+ * access checks. SERVER-ONLY — a PATCH carrying the key is a 400
53
+ * `invalid_field`.
54
+ */
55
+ owner?: string;
56
+ /** The user-facing session name; absent = unnamed (T5d). */
57
+ name?: string;
58
+ /**
59
+ * Server-owned automatic name (the auto-namer writes it, A2); the
60
+ * listing serves it only while `name` is unset. NEVER patchable — the
61
+ * strict validator rejects the key as `invalid_field`.
62
+ */
63
+ autoName?: string;
64
+ /** Server-owned: the auto-namer's attempt count (A2). Never patchable. */
65
+ autoNameAttempts?: number;
66
+ /**
67
+ * Server-owned: the last activity instant, ISO 8601 (`activity.ts`
68
+ * stamps it at submission admission and run settlement). Never
69
+ * patchable — the strict validator rejects the key as `invalid_field`.
70
+ */
71
+ lastActivityAt?: string;
72
+ /**
73
+ * The §5.9 per-resource sharing state; absent = private (owner and
74
+ * superuser only). Patchable by the OWNER or a superuser (403
75
+ * otherwise); `sharing: null` clears back to private.
76
+ */
77
+ sharing?: Sharing;
78
+ };
79
+ /** A validated PATCH body: present keys apply, `null` clears to default. */
80
+ export interface SessionMetaPatch {
81
+ readonly pinned?: boolean | null;
82
+ readonly tagged?: readonly string[] | null;
83
+ readonly archived?: boolean | null;
84
+ readonly draft?: {
85
+ readonly text?: string | null;
86
+ readonly attachments?: readonly string[] | null;
87
+ } | null;
88
+ readonly name?: string | null;
89
+ /**
90
+ * The §5.9 sharing set/clear (`null` → private). `owner` and `source`
91
+ * are deliberately ABSENT — the validator rejects them as
92
+ * `invalid_field`, so a patch type that cannot express them keeps the
93
+ * apply step honest by construction.
94
+ */
95
+ readonly sharing?: Sharing | null;
96
+ }
97
+ /** Why a PATCH body was rejected (the configure endpoint's error contract). */
98
+ export type SessionMetaPatchError = {
99
+ readonly error: "invalid_body";
100
+ } | {
101
+ readonly error: "invalid_field";
102
+ readonly field: string;
103
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The session-meta WIRE types (spec §5) — the client-facing half of
3
+ * `src/server/session-meta.ts` (which owns the document/patch runtime).
4
+ *
5
+ * This module is part of the shared leaf package (`@sigrun/shared`): the
6
+ * tui and webui import these shapes from here, never from server source
7
+ * (the load-bearing decoupling — the tui is a pure client of the wire).
8
+ *
9
+ * PURE module: no imports at all (the views-purity guard enforces this
10
+ * across `shared/src`) — the `import type` below is the sanctioned wire-
11
+ * module exception: sibling-only (`./access.js`), type-only.
12
+ */
13
+ export {};
@@ -0,0 +1,401 @@
1
+ /**
2
+ * The settings-file TYPES (Task 8a, pi parity spec: the settings file) —
3
+ * the persisted `SettingsFile` shape and its sections.
4
+ *
5
+ * The CANONICAL type declarations live here (the shared leaf package);
6
+ * `src/harness/settings.ts` imports them (type-only) for the store/
7
+ * validation runtime. The `GET/PATCH /v1/settings` wire serves this
8
+ * shape verbatim (`SettingsResponse` = `SettingsFile` + the
9
+ * server-computed `toolRoster`; see `@sigrun/shared/views`), so both
10
+ * clients read these fields straight off the wire.
11
+ *
12
+ * The file is a JSON document at `<config-dir>/settings.json`. It
13
+ * carries the `HarnessSettings` pass-throughs (`steeringMode`,
14
+ * `followUpMode`, `retry`, `stream`) plus sigrun's own resolved fields
15
+ * (`defaultTools`, `perModelThinking`, `extensions`, `jobs`, `jobHistory`,
16
+ * `defaultModel`, `namingModel`, `defaultProvider`, `server`). Credentials
17
+ * never live here (they live in the sibling `auth.json`).
18
+ */
19
+ import type { JobSpec } from "./jobs.js";
20
+ /**
21
+ * One entry of the unified `extensions` list: an install-and-load
22
+ * declaration. The string form is shorthand for `{source}`; the object
23
+ * form adds the git/entry/enabled knobs. See the `extensions` field
24
+ * docblock on {@link SettingsFile} for the full grammar.
25
+ */
26
+ export type ExtensionSpec = string | {
27
+ /** The source: npm specifier, bare name, file:/path, or git source. */
28
+ readonly source: string;
29
+ /** Entry-point override (path relative to the resolved source root). */
30
+ readonly entry?: string;
31
+ /** Git ref (same as the `@ref` suffix, spelled out). */
32
+ readonly ref?: string;
33
+ /** Default true; `false` = directory state retained, not loaded. */
34
+ readonly enabled?: boolean;
35
+ };
36
+ /**
37
+ * The job-history retention limits (spec §8.1, ruling 14), applied per
38
+ * job at run-settle and revision-write: keep the newest `runs` run
39
+ * records and the newest `revisions` revisions (plus any revision a
40
+ * retained run references). Live settings — both positive integers,
41
+ * default 50.
42
+ */
43
+ export interface JobHistorySettings {
44
+ readonly runs?: number;
45
+ readonly revisions?: number;
46
+ }
47
+ /**
48
+ * The `server` section: static boot configuration mirrored from the
49
+ * `SIGRUN_*` env knobs. Every field is optional; a present field is the
50
+ * deployment default applied when the corresponding env var is ABSENT
51
+ * (env always wins, validated first). Not here: the extensions list and
52
+ * the model (top-level fields of {@link SettingsFile}).
53
+ */
54
+ export interface ServerSettings {
55
+ /** `SIGRUN_PORT` — a positive integer. */
56
+ readonly port?: number;
57
+ /** `SIGRUN_WORKSPACE_PATH` — a non-empty path. */
58
+ readonly workspacePath?: string;
59
+ /** `SIGRUN_STORAGE_PATH` — a non-empty path. */
60
+ readonly storagePath?: string;
61
+ /** `SIGRUN_EXTENSIONS_DIR` — a non-empty path. */
62
+ readonly extensionsDir?: string;
63
+ /**
64
+ * `SIGRUN_MAX_UPLOAD_BYTES` — a positive integer. The raw-upload
65
+ * PUT route's body limit; the base64 POST and the JSONL import derive
66
+ * theirs from it (the 4/3 base64 inflation plus the JSON wrapper).
67
+ * Default 64 MiB (spec §6.2).
68
+ */
69
+ readonly maxUploadBytes?: number;
70
+ /**
71
+ * `SIGRUN_MAX_FILE_READ_BYTES` — a positive integer. The raw
72
+ * file-read cap (`GET .../files/raw`): 413 `file_too_large` beyond
73
+ * it. Default 16 MiB (spec §7.8) — the agent writes workspace
74
+ * files, so nothing else bounds them.
75
+ */
76
+ readonly maxFileReadBytes?: number;
77
+ /**
78
+ * `SIGRUN_DATA_DIR` — a non-empty path. Applied where the env var is
79
+ * absent; the env-or-file value wins VERBATIM (no `/sigrun` suffix),
80
+ * exactly like the stores resolver's explicit tier. The DB default
81
+ * and relative `SIGRUN_STORAGE_PATH` values resolve under this root.
82
+ */
83
+ readonly dataDir?: string;
84
+ /** `SIGRUN_CACHE_DIR` — a non-empty path, verbatim (see `dataDir`). */
85
+ readonly cacheDir?: string;
86
+ /** `SIGRUN_STATE_DIR` — a non-empty path, verbatim (see `dataDir`). */
87
+ readonly stateDir?: string;
88
+ /**
89
+ * `SIGRUN_PUBLIC_URL` (core hardening W5 Task 2, spec §9.2): the
90
+ * deployment's absolute base URL — http(s) with a hostname, any
91
+ * trailing slash stripped at resolution. `undefined` when neither
92
+ * env nor file sets it; the extension-route adapter bases handlers'
93
+ * absolute `Request` URLs on it and notification deep links are
94
+ * omitted without it.
95
+ */
96
+ publicUrl?: string;
97
+ /**
98
+ * The webui channel's gate + bundle selector (build-and-webui Task 4):
99
+ * `SIGRUN_WEBUI` maps to `enabled` (strict true/false parse), the
100
+ * selector comes from `SIGRUN_WEBUI_BUNDLE` over `bundle` over the
101
+ * legacy `webuiBundle` (deprecated alias, one release). Both keys are
102
+ * OPTIONAL — the settings bootstrap OMITS the section entirely when
103
+ * fully defaulted (a mirrored default would read as an explicit
104
+ * selection on the next boot and make absent-default → API-only
105
+ * unreachable).
106
+ */
107
+ readonly webui?: {
108
+ /** `SIGRUN_WEBUI` — boolean, default true. `false` = API-only. */
109
+ readonly enabled?: boolean;
110
+ /**
111
+ * The bundle selector (non-empty string): a contributed-bundle NAME
112
+ * or a PATH, discriminated syntactically at serve time (Task 5).
113
+ * Absent = the built-in default bundle.
114
+ */
115
+ readonly bundle?: string;
116
+ };
117
+ /**
118
+ * DEPRECATED ALIAS for `webui.bundle` (build-and-webui Task 4), kept
119
+ * for one release: `SIGRUN_WEBUI_BUNDLE` over `webui.bundle` over THIS
120
+ * key over the default. Accepted by validation with a deprecation
121
+ * warn; the bootstrap never writes it.
122
+ *
123
+ * @deprecated use `webui.bundle`
124
+ */
125
+ readonly webuiBundle?: string;
126
+ /**
127
+ * The timezone `SIGRUN_TIMEZONE` mirrors (jobs §8.2): an IANA name —
128
+ * the default timezone for cron schedules that declare none (default:
129
+ * the process TZ). Validated as an IANA name (an `Intl.DateTimeFormat`
130
+ * construction succeeds).
131
+ */
132
+ readonly timezone?: string;
133
+ /** The `SIGRUN_COMPACTION_*` policy knobs. */
134
+ readonly compaction?: {
135
+ readonly enabled?: boolean;
136
+ readonly reserveTokens?: number;
137
+ readonly keepRecentTokens?: number;
138
+ readonly backgroundTokens?: number;
139
+ };
140
+ /**
141
+ * The memory-seam bounds (memory-seam Task 6), mirroring the
142
+ * `SIGRUN_{HOOK_DEADLINE,IDLE_THRESHOLD,WORKER_DEADLINE,QUEUE_MAX}`
143
+ * env knobs — each a positive integer applied per-field where the
144
+ * corresponding env var is absent.
145
+ */
146
+ readonly memorySeam?: {
147
+ readonly hookDeadlineMs?: number;
148
+ readonly idleThresholdMs?: number;
149
+ readonly workerDeadlineMs?: number;
150
+ readonly queueMax?: number;
151
+ };
152
+ /**
153
+ * The provider plugins' runtime knobs (provider plugins Task 5):
154
+ * `SIGRUN_PROVIDER_REFRESH_MINUTES` — the interval refresh cadence
155
+ * in minutes, a non-negative integer where `0` disables the
156
+ * scheduler. Applied per-field where the env var is absent.
157
+ */
158
+ readonly providers?: {
159
+ readonly refreshIntervalMinutes?: number;
160
+ };
161
+ /**
162
+ * The auth surface (core-hardening W1, spec §5.2/§5.3): the
163
+ * authenticator method list, the forward-auth (proxy) config, the
164
+ * admin group mapping, and the Host/Origin allowlists. Mirrors the
165
+ * `SIGRUN_AUTH`/`SIGRUN_AUTH_TRUSTED_PROXIES` env knobs (the env
166
+ * service token `SIGRUN_API_TOKEN` and the shared secret
167
+ * `SIGRUN_PROXY_SECRET` are ENV-ONLY credentials — validation rejects
168
+ * a `secret` key here, key material never lives in this file).
169
+ * `methods` ABSENT means derive from the listen host (`[]` on
170
+ * loopback, `["token"]` otherwise) — which is why a defaulted auth
171
+ * section is omitted from the bootstrap projection entirely (an
172
+ * explicit `methods: []` would read as intent on the next boot).
173
+ */
174
+ readonly auth?: {
175
+ /** `SIGRUN_AUTH` — csv of `token`/`proxy`; absent = derive from the listen host. */
176
+ readonly methods?: readonly ("token" | "proxy")[];
177
+ readonly proxy?: {
178
+ readonly trustedProxies?: readonly string[];
179
+ readonly secretHeader?: string;
180
+ readonly userHeader?: string;
181
+ readonly groupsHeader?: string;
182
+ readonly nameHeader?: string;
183
+ readonly emailHeader?: string;
184
+ readonly autoProvision?: boolean;
185
+ };
186
+ readonly adminGroups?: readonly string[];
187
+ readonly allowedHosts?: readonly string[];
188
+ readonly allowedOrigins?: readonly string[];
189
+ };
190
+ /**
191
+ * `/metrics` exposure (core-hardening W1): `SIGRUN_METRICS_PUBLIC`
192
+ * over this key, default `false` (a principal is required). Omitted
193
+ * from the bootstrap projection when defaulted (the webui pattern).
194
+ */
195
+ readonly metrics?: {
196
+ readonly public?: boolean;
197
+ };
198
+ /**
199
+ * The backup target — exactly one of the two shapes (mirroring the
200
+ * env rule: `SIGRUN_BACKUP_PATH` XOR `SIGRUN_BACKUP_S3_BUCKET`), plus
201
+ * the §6.8 honesty knob: `staleAfterHours`, how old the
202
+ * `backup/last-success.json` marker may be before `health.backup`
203
+ * reports `"degraded"` (positive number; no env var exists — file or
204
+ * the 36 h code default).
205
+ */
206
+ readonly backup?: {
207
+ readonly path?: string;
208
+ readonly staleAfterHours?: number;
209
+ } | {
210
+ readonly s3?: {
211
+ readonly bucket?: string;
212
+ readonly prefix?: string;
213
+ readonly endpoint?: string;
214
+ };
215
+ readonly staleAfterHours?: number;
216
+ };
217
+ }
218
+ /**
219
+ * Which tier supplied a resolved server key (the `/v1/settings`
220
+ * `effective` block, spec §6.6): the env var was set (`"env"`), the
221
+ * settings file's `server` section set it (`"file"`), or neither and
222
+ * the code default applied (`"default"`).
223
+ */
224
+ export type ServerSettingSource = "env" | "file" | "default";
225
+ /**
226
+ * One `effective` entry: the RESOLVED value (what boot actually used)
227
+ * plus the {@link ServerSettingSource} that won. Keys are flattened
228
+ * dotted paths (`"server.port"`, `"server.compaction.reserveTokens"`).
229
+ */
230
+ export interface EffectiveServerSetting {
231
+ readonly value: unknown;
232
+ readonly source: ServerSettingSource;
233
+ }
234
+ /**
235
+ * The degenerate env-driven provider's static configuration (ruling 7).
236
+ * Only the endpoint is here — credentials live in `auth.json`.
237
+ */
238
+ export interface DefaultProviderSettings {
239
+ /** `OPENAI_BASE_URL` — a non-empty URL. */
240
+ readonly baseUrl?: string;
241
+ }
242
+ /**
243
+ * The skills surface's settings (core hardening W5 Task 4, spec §9.4) —
244
+ * live, read per skills-section render.
245
+ */
246
+ export interface SkillsSettings {
247
+ /** The rendered-section byte cap (default 4096, notice included). */
248
+ readonly sectionMaxBytes?: number;
249
+ }
250
+ /**
251
+ * The notifications surface's settings (core hardening W5 Task 6, spec
252
+ * §9.6) — live, read per event.
253
+ */
254
+ export interface NotificationsSettings {
255
+ /**
256
+ * When a `run.settled` notification fires: `"unwatched"` (default)
257
+ * only when no client stream is attached to the conversation (the
258
+ * away-from-desk case), `"cron"` only for job-run conversations,
259
+ * `"always"` every settle, `"never"` none.
260
+ */
261
+ readonly runSettled?: "unwatched" | "cron" | "always" | "never";
262
+ }
263
+ /**
264
+ * The built-in extensions toggles (Task 2, pi parity): codemode, MCP and
265
+ * tool search ship WITH core. Each key defaults to `true`; `false`
266
+ * removes the built-in entirely (absent from `/v1/extensions`, its config
267
+ * ignored with one log line). RESTART-REQUIRED: the built-in set resolves
268
+ * at boot, like `extensions`.
269
+ */
270
+ export interface BuiltinExtensionsSettings {
271
+ readonly codemode?: boolean;
272
+ readonly mcp?: boolean;
273
+ readonly toolSearch?: boolean;
274
+ }
275
+ /**
276
+ * The persisted shape: HarnessSettings pass-throughs + sigrun fields.
277
+ */
278
+ export interface SettingsFile {
279
+ /** pi-durable `HarnessSettings.steeringMode`. */
280
+ readonly steeringMode?: "all" | "one-at-a-time";
281
+ /** pi-durable `HarnessSettings.followUpMode`. */
282
+ readonly followUpMode?: "all" | "one-at-a-time";
283
+ /** pi-durable `HarnessSettings.retry` (partial policy, merged over defaults). */
284
+ readonly retry?: {
285
+ readonly enabled?: boolean;
286
+ readonly maxRetries?: number;
287
+ readonly baseDelayMs?: number;
288
+ readonly maxAgentDelayMs?: number;
289
+ };
290
+ /** pi-durable `HarnessSettings.stream` (partial, merged over defaults). */
291
+ readonly stream?: {
292
+ readonly timeoutMs?: number;
293
+ readonly maxRetries?: number;
294
+ readonly maxRetryDelayMs?: number;
295
+ };
296
+ /**
297
+ * Sigrun: tool-name filter applied at conversation creation; maps to
298
+ * pi-durable 1.3.0's `AgentChange.modelTools` (the model-offered
299
+ * filter — never the enabled filter `AgentChange.tools`).
300
+ */
301
+ readonly defaultTools?: readonly string[];
302
+ /**
303
+ * Sigrun: per-model thinking-level overrides, keyed by
304
+ * `"provider/modelId"`.
305
+ */
306
+ readonly perModelThinking?: Readonly<Record<string, string>>;
307
+ /**
308
+ * Sigrun: the unified extension list — each entry is the DESIRED
309
+ * INSTALL+LOAD state (one list, no install/load split). Entry forms:
310
+ *
311
+ * - a string: an npm specifier ("name", "name@range",
312
+ * "@scope/name@range"), a bare name (app-tree/vendor shorthand,
313
+ * load-only), a `file:`/absolute path (loaded in place, nothing
314
+ * installed), or a git source ("git:host/path" or an https git
315
+ * URL, with an optional `@ref` suffix) — cloned into
316
+ * `<config-dir>/git/` and loaded after entry discovery.
317
+ * - an object: `{ source, entry?, ref?, enabled? }` — the explicit
318
+ * form. `entry` overrides entry discovery (path relative to the
319
+ * resolved source root); `ref` is the git ref (same as the `@ref`
320
+ * suffix, spelled out); `enabled: false` keeps the directory
321
+ * state but does not load (the installed-but-disabled case).
322
+ *
323
+ * npm + git entries are installed/fetched at boot by `syncPackages`
324
+ * into the packages/git dirs under the config dir; REMOVING an entry
325
+ * uninstalls it. Applied when `SIGRUN_EXTENSIONS` is absent.
326
+ * Changes require a restart.
327
+ */
328
+ readonly extensions?: readonly ExtensionSpec[];
329
+ /**
330
+ * Sigrun: the file-declared job list (spec §8.3 — the `file` source).
331
+ * Each entry is a `JobSpec` validated with `validateJobSpec` (the
332
+ * jobs §8.2 grammar); names double as the id suffix (`file:<name>`).
333
+ * Duplicate names are a validation error. Restart-required: like
334
+ * `extensions`, the list resolves at boot reconciliation.
335
+ */
336
+ readonly jobs?: readonly JobSpec[];
337
+ /**
338
+ * Sigrun: per-job history retention (spec §8.1, ruling 14) — LIVE
339
+ * (read per prune, not restart-required): keep the newest `runs` run
340
+ * records and the newest `revisions` revisions per job, plus any
341
+ * revision a retained run references. Both positive integers,
342
+ * default 50 each.
343
+ */
344
+ readonly jobHistory?: JobHistorySettings;
345
+ /**
346
+ * Sigrun: the boot default model (ruling 3), a non-empty
347
+ * `provider/model-id` spec parsed exactly like `SIGRUN_DEFAULT_MODEL`
348
+ * (bare id implies the `openai` provider). Applied when the env var is
349
+ * absent; REQUIRED overall — neither env nor file is a fail-loud boot
350
+ * error, no code default.
351
+ */
352
+ readonly defaultModel?: string;
353
+ /**
354
+ * Sigrun: the model the auto-namer (UI refresh A2) completes through, a
355
+ * non-empty `provider/model-id` spec validated exactly like
356
+ * `defaultModel`, or `false` to disable automatic naming. Absent = the
357
+ * boot default model. Read live per settled run, so a PATCH reaches the
358
+ * next naming call without a restart.
359
+ */
360
+ readonly namingModel?: string | false;
361
+ /**
362
+ * Sigrun: the env-driven OpenAI-compatible provider's endpoint
363
+ * (ruling 7), applied when `OPENAI_BASE_URL` is absent.
364
+ */
365
+ readonly defaultProvider?: DefaultProviderSettings;
366
+ /**
367
+ * Sigrun: the skills surface's knob (core hardening W5 Task 4, spec
368
+ * §9.4). `sectionMaxBytes` caps the rendered skills section (default
369
+ * 4096, truncation notice included). LIVE — read per render, so a
370
+ * PATCH reaches the next prompt without a restart.
371
+ */
372
+ readonly skills?: SkillsSettings;
373
+ /**
374
+ * Sigrun: the notifications surface's knob (core hardening W5 Task 6,
375
+ * spec §9.6). `runSettled` gates when a settled run notifies (default
376
+ * `"unwatched"`). LIVE — read per event, so a PATCH reaches the next
377
+ * settle without a restart.
378
+ */
379
+ readonly notifications?: NotificationsSettings;
380
+ /**
381
+ * Sigrun: the built-in extensions toggles (Task 2). Each defaults to
382
+ * `true`. RESTART-REQUIRED (resolved at boot).
383
+ */
384
+ readonly builtinExtensions?: BuiltinExtensionsSettings;
385
+ /**
386
+ * Sigrun: the MCP-driven auto-enable (Task 2, ruling 4). Default
387
+ * `true`: when at least one enabled MCP server's effective exposure is
388
+ * `codemode`, the `codemode` tool stays in the default model-offered
389
+ * set for newly created conversations; when any is `deferred`,
390
+ * `tool_search` stays offered. `false` turns both arms off. An
391
+ * explicit `defaultTools` filter always wins (auto-enable only edits
392
+ * the DEFAULT set). LIVE — read at conversation create, like
393
+ * `defaultTools`.
394
+ */
395
+ readonly autoEnableCodemode?: boolean;
396
+ /**
397
+ * Sigrun: the static boot configuration (see {@link ServerSettings}).
398
+ * Deployment defaults under env — changes require a restart.
399
+ */
400
+ readonly server?: ServerSettings;
401
+ }