@sigrun/sigrun 0.8.0 → 0.9.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 (43) hide show
  1. package/CHANGELOG.md +35 -87
  2. package/dist/config.js +47 -0
  3. package/dist/curation/host.js +125 -21
  4. package/dist/curation/runs.js +15 -4
  5. package/dist/extension.d.ts +268 -3
  6. package/dist/harness/boot.js +113 -3
  7. package/dist/harness/complete.js +127 -0
  8. package/dist/harness/decision-tool.js +93 -0
  9. package/dist/harness/decisions.js +477 -0
  10. package/dist/harness/event-bus.js +12 -0
  11. package/dist/harness/interactions.js +81 -0
  12. package/dist/harness/interactive-tools.js +97 -0
  13. package/dist/harness/loader.js +243 -1
  14. package/dist/harness/packages.js +7 -0
  15. package/dist/harness/published-hooks.js +208 -0
  16. package/dist/harness/seam-host.js +136 -1
  17. package/dist/harness/settings.js +41 -0
  18. package/dist/history/git.js +6 -2
  19. package/dist/jobs/fire.js +10 -0
  20. package/dist/jobs/observe.js +27 -2
  21. package/dist/server/conversation-ops.js +31 -0
  22. package/dist/server/core-views/skills-tree.js +3 -12
  23. package/dist/server/core-views/skills.js +11 -23
  24. package/dist/server/host-bridge.js +53 -1
  25. package/dist/server/routes/extensions.js +5 -0
  26. package/dist/server/routes/interactions.js +48 -1
  27. package/dist/server/routes/meta.js +17 -0
  28. package/dist/server/routes/providers.js +65 -8
  29. package/dist/server/routes/submissions.js +3 -0
  30. package/dist/settings/registry.js +18 -0
  31. package/node_modules/@sigrun/shared/CHANGELOG.md +7 -0
  32. package/node_modules/@sigrun/shared/dist/decisions.d.ts +95 -0
  33. package/node_modules/@sigrun/shared/dist/decisions.js +16 -0
  34. package/node_modules/@sigrun/shared/dist/interactions.d.ts +14 -0
  35. package/node_modules/@sigrun/shared/dist/settings.d.ts +32 -0
  36. package/node_modules/@sigrun/shared/dist/views.d.ts +58 -0
  37. package/node_modules/@sigrun/shared/package.json +6 -2
  38. package/package.json +6 -6
  39. package/webui/dist/assets/index-D5KowS7k.css +1 -0
  40. package/webui/dist/assets/index-DGIrLC6F.js +55 -0
  41. package/webui/dist/index.html +2 -2
  42. package/webui/dist/assets/index-B3uMNF59.css +0 -1
  43. package/webui/dist/assets/index-BXsCyod4.js +0 -55
@@ -73,6 +73,12 @@ export function registerMetaRoutes(app, ctx) {
73
73
  // The §7.3 shared-data flag: channels refetch
74
74
  // fully when a since fetch answers ops-only.
75
75
  ...(def.sharedData === true ? { sharedData: true } : {}),
76
+ // The per-conversation opt-in: channels attach the
77
+ // panel with the open conversation's id (sparse —
78
+ // present only when the panel opted in).
79
+ ...(def.perConversation === true
80
+ ? { perConversation: true }
81
+ : {}),
76
82
  })),
77
83
  }),
78
84
  };
@@ -174,6 +180,17 @@ export function registerMetaRoutes(app, ctx) {
174
180
  providers: opened.providerCollisions,
175
181
  commands: commandCatalog.collisions,
176
182
  },
183
+ // Knowledge C7: the published-hook registry — one row per OWNED
184
+ // hook name (`provider` is the owner slug; `shadowed` lists the
185
+ // losing providers). Empty when nothing published.
186
+ publishedHooks: opened.publishedHooks(),
187
+ // Warden Task 7: the decision-provider seam projection (the same
188
+ // pure-derivation discipline — the registry's boot-built rows).
189
+ decisions: {
190
+ providers: opened.decisions.providers(),
191
+ default: opened.decisions.resolvedDefault(),
192
+ collisions: opened.decisions.collisions(),
193
+ },
177
194
  };
178
195
  });
179
196
  // The slash-command catalog: the thin metadata list the composer's `/`
@@ -87,8 +87,30 @@ function providerReadinessReport(opened) {
87
87
  for (const { id: providerId } of opened.models.getProviders()) {
88
88
  providers.push(readinessEntry(opened, providerId, producersByName));
89
89
  }
90
+ // Warden Task 7: decision providers append as their own entries
91
+ // (`kind: "decision"`, the model ids listed) — a decision provider
92
+ // whose id collides with a chat provider stays SEPARATE (distinct
93
+ // kinds, never merged).
94
+ for (const decision of opened.decisions.providers()) {
95
+ providers.push(decisionEntry(decision));
96
+ }
90
97
  return { providers };
91
98
  }
99
+ /**
100
+ * One decision provider's report entry (warden Task 7): the registry
101
+ * row projected onto the shared entry shape — `models` the count,
102
+ * `modelIds` the bare ids, `owner` the contributing extension.
103
+ */
104
+ function decisionEntry(decision) {
105
+ return {
106
+ provider: decision.id,
107
+ kind: "decision",
108
+ status: decision.status,
109
+ models: decision.models.length,
110
+ modelIds: decision.models,
111
+ owner: decision.extension,
112
+ };
113
+ }
92
114
  /** Register the model-catalog and provider-readiness routes. */
93
115
  export function registerProviderRoutes(app, ctx) {
94
116
  const { opened } = ctx;
@@ -109,6 +131,35 @@ export function registerProviderRoutes(app, ctx) {
109
131
  // serves, so the 404 vocabulary matches it). Key material never
110
132
  // appears in any of these routes — statuses and counts only.
111
133
  const isKnownProvider = (providerId) => opened.models.getProvider(providerId) !== undefined;
134
+ // Warden Task 7: a DECISION-only provider id is equally routable (the
135
+ // registry enumerates its ids). The mutation bodies branch on the
136
+ // kind: chat ids refresh, decision ids re-read — the registry
137
+ // derives readiness per call, so a credential write is visible
138
+ // without a refresh (and the decision seam has no catalog leg to
139
+ // refresh).
140
+ const isDecisionProvider = (providerId) => opened.decisions.providers().some((row) => row.id === providerId);
141
+ const isRoutableProvider = (providerId) => isKnownProvider(providerId) || isDecisionProvider(providerId);
142
+ // The entry back from a mutation: the chat derivation for catalog
143
+ // providers, the decision entry for decision-only ids (a chat AND
144
+ // decision provider sharing an id answer the CHAT entry — the
145
+ // mutation there targets the catalog provider's refresh leg).
146
+ const routableEntry = (providerId) => {
147
+ if (isKnownProvider(providerId)) {
148
+ return readinessEntry(opened, providerId, producersIndex(opened));
149
+ }
150
+ const decision = opened.decisions
151
+ .providers()
152
+ .find((row) => row.id === providerId);
153
+ if (decision === undefined) {
154
+ throw new Error(`provider "${providerId}" is not routable`);
155
+ }
156
+ return decisionEntry(decision);
157
+ };
158
+ // The post-write refresh: chat ids only.
159
+ const refreshAfterWrite = async (providerId) => {
160
+ if (isKnownProvider(providerId))
161
+ await opened.refreshProvider(providerId);
162
+ };
112
163
  // One bounded refresh of the named provider (W3 Task 6, spec §7.6):
113
164
  // the SAME policy the boot/interval/refresh-on-miss paths run — bounded
114
165
  // by the deadline, failures warned (never the route's error), a
@@ -138,7 +189,7 @@ export function registerProviderRoutes(app, ctx) {
138
189
  return await reply.code(403).send({ error: "forbidden" });
139
190
  }
140
191
  const { id } = request.params;
141
- if (!isKnownProvider(id)) {
192
+ if (!isRoutableProvider(id)) {
142
193
  return await reply.code(404).send({ error: "provider_not_found" });
143
194
  }
144
195
  const body = request.body;
@@ -153,9 +204,14 @@ export function registerProviderRoutes(app, ctx) {
153
204
  .code(400)
154
205
  .send({ error: "invalid_field", field: "apiKey" });
155
206
  }
156
- await opened.auth.setApiKey(credentialKey(id), apiKey);
157
- await opened.refreshProvider(id);
158
- return readinessEntry(opened, id, producersIndex(opened));
207
+ // The credential key: chat ids redirect the DEFAULT provider's id
208
+ // to the magic "default" key; decision-only ids use the id
209
+ // verbatim (the magic key names the default CHAT provider, never a
210
+ // decision credential).
211
+ const key = isKnownProvider(id) ? credentialKey(id) : id;
212
+ await opened.auth.setApiKey(key, apiKey);
213
+ await refreshAfterWrite(id);
214
+ return routableEntry(id);
159
215
  });
160
216
  // Remove the provider's credential (admin): store delete of that SAME
161
217
  // key → one refresh → the entry. Restart-free the same way — the
@@ -165,12 +221,13 @@ export function registerProviderRoutes(app, ctx) {
165
221
  return await reply.code(403).send({ error: "forbidden" });
166
222
  }
167
223
  const { id } = request.params;
168
- if (!isKnownProvider(id)) {
224
+ if (!isRoutableProvider(id)) {
169
225
  return await reply.code(404).send({ error: "provider_not_found" });
170
226
  }
171
- await opened.auth.deleteApiKey(credentialKey(id));
172
- await opened.refreshProvider(id);
173
- return readinessEntry(opened, id, producersIndex(opened));
227
+ const key = isKnownProvider(id) ? credentialKey(id) : id;
228
+ await opened.auth.deleteApiKey(key);
229
+ await refreshAfterWrite(id);
230
+ return routableEntry(id);
174
231
  });
175
232
  // Models: the merged chat-model catalog as picker entries (no provider
176
233
  // internals — baseUrl/headers/auth stay server-side). The shared
@@ -52,6 +52,9 @@ export function registerSubmissionRoutes(app, ctx) {
52
52
  ? {}
53
53
  : { user: request.principal.user }),
54
54
  ...(body.whenBusy === undefined ? {} : { whenBusy: body.whenBusy }),
55
+ // Ask-user Task 2: a human's message through the HTTP submit
56
+ // route declines pending declinable questions (spec §5.3).
57
+ declinesQuestions: true,
55
58
  ...(request.query?.waitMs ===
56
59
  undefined
57
60
  ? {}
@@ -133,6 +133,18 @@ export const CORE_SETTING_KEYS = [
133
133
  section: "general",
134
134
  reason: "read per settled run by the auto-namer (src/harness/naming.ts)",
135
135
  },
136
+ {
137
+ key: "decisionModel",
138
+ apply: "live",
139
+ section: "general",
140
+ reason: "read per host.decisions.evaluate call by the decision registry (src/harness/decisions.ts)",
141
+ },
142
+ {
143
+ key: "decisions.tool",
144
+ apply: "live",
145
+ section: "tools",
146
+ reason: "read at each decision_evaluate tool call (default off)",
147
+ },
136
148
  {
137
149
  key: "defaultProvider.baseUrl",
138
150
  apply: "restart",
@@ -332,6 +344,12 @@ export const CORE_SETTING_KEYS = [
332
344
  section: "advanced",
333
345
  reason: "the event bus's queue bound is set at open (boot)",
334
346
  },
347
+ {
348
+ key: "server.decisions.timeoutMs",
349
+ apply: "live",
350
+ section: "providers",
351
+ reason: "the decision registry reads the settings' timeoutMs per evaluate call (src/harness/decisions.ts)",
352
+ },
335
353
  {
336
354
  key: "server.providers.refreshIntervalMinutes",
337
355
  apply: "live",
@@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
 
9
+ ## [0.9.0] - 2026-11-05
10
+
11
+ - `InteractionRecord` gains optional `declinable: true` and settled `declinedVia:
12
+ "button" | "message"` (the ask-user decline wire surface — additive, optional).
13
+ - `InteractionDeclined` wire type beside `InteractionAnswered` (the decline route's
14
+ 200 body).
15
+
9
16
  ## [0.8.0] - 2026-11-04
10
17
 
11
18
  - New `./notices` subpath: the `UserNotice*` wire types, `NOTICE_LIMITS`, and the
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The decision-provider wire types (the decision-providers spec): a
3
+ * provider TYPE whose models answer typed questions (noul / choice /
4
+ * score) over a JSON state, for extensions that need small structured
5
+ * judgments rather than a chat completion.
6
+ *
7
+ * TYPE-ONLY module, with NO imports at all: `JsonValue`/`JsonObject` are
8
+ * declared locally exactly the way `shared/src/json.ts` mirrors them, so
9
+ * the shared leaf keeps its zero-import invariant (enforced by
10
+ * test/views-purity.test.ts). The two `DECISION_MAX_*` constants are
11
+ * pure VALUES — wire modules carry "types + pure constants".
12
+ */
13
+ /** A strict-JSON value (local mirror — see shared/src/json.ts). */
14
+ export type DecisionJsonValue = null | boolean | number | string | DecisionJsonValue[] | {
15
+ [key: string]: DecisionJsonValue;
16
+ };
17
+ /** A strict-JSON object (local mirror — see shared/src/json.ts). */
18
+ export type DecisionJsonObject = {
19
+ [key: string]: DecisionJsonValue;
20
+ };
21
+ /** The state a decision question is asked over: a string or strict JSON. */
22
+ export type DecisionState = string | DecisionJsonObject | DecisionJsonValue[];
23
+ /** One typed question. Choice needs at least one label; score at least two levels. */
24
+ export type DecisionQuestion = {
25
+ readonly type: "noul";
26
+ readonly instructions?: DecisionJsonValue;
27
+ readonly criteria?: {
28
+ readonly true?: DecisionJsonValue;
29
+ readonly false?: DecisionJsonValue;
30
+ } | null;
31
+ } | {
32
+ readonly type: "choice";
33
+ readonly instructions?: DecisionJsonValue;
34
+ readonly criteria: Readonly<Record<string, DecisionJsonValue | null>>;
35
+ } | {
36
+ readonly type: "score";
37
+ readonly instructions?: DecisionJsonValue;
38
+ readonly criteria: readonly DecisionJsonValue[];
39
+ };
40
+ /** One evaluation: a state plus 1..32 typed questions. */
41
+ export interface DecisionRequest {
42
+ readonly state: DecisionState;
43
+ readonly questions: Readonly<Record<string, DecisionQuestion>>;
44
+ }
45
+ /** One answer, matching its question's type; probabilities are in [0, 1]. */
46
+ export type DecisionAnswer = {
47
+ readonly type: "noul";
48
+ readonly noul: number;
49
+ } | {
50
+ readonly type: "choice";
51
+ readonly choice: string;
52
+ readonly confidence: number;
53
+ readonly probabilities: Readonly<Record<string, number>>;
54
+ } | {
55
+ readonly type: "score";
56
+ readonly score: number;
57
+ readonly confidence: number;
58
+ readonly probabilities: Readonly<Record<string, number>>;
59
+ readonly legend?: Readonly<Record<string, DecisionJsonValue>>;
60
+ };
61
+ /** The provider's answer set plus optional usage bookkeeping. */
62
+ export interface DecisionAnswers {
63
+ readonly answers: Readonly<Record<string, DecisionAnswer>>;
64
+ readonly usage?: {
65
+ readonly input_tokens: number | null;
66
+ readonly output_tokens: number | null;
67
+ };
68
+ readonly model?: string;
69
+ }
70
+ /** The outcome of one `evaluate` call; NEVER a throw. */
71
+ export type DecisionResult = {
72
+ readonly ok: true;
73
+ readonly model: string;
74
+ readonly answers: Readonly<Record<string, DecisionAnswer>>;
75
+ readonly usage: {
76
+ readonly input_tokens: number | null;
77
+ readonly output_tokens: number | null;
78
+ };
79
+ readonly elapsedMs: number;
80
+ } | {
81
+ readonly ok: false;
82
+ readonly code: "no_model" | "unknown_model" | "invalid_request" | "over_limits" | "not_configured" | "timeout" | "aborted" | "failed" | "bad_answer";
83
+ readonly error: string;
84
+ };
85
+ /** A model's declared limits (narrower than the built-in caps win). */
86
+ export interface DecisionLimits {
87
+ readonly maxQuestions?: number;
88
+ readonly maxInputBytes?: number;
89
+ readonly minChoiceOptions?: number;
90
+ readonly maxScoreLevels?: number;
91
+ }
92
+ /** Built-in cap: at most this many questions per request. */
93
+ export declare const DECISION_MAX_QUESTIONS = 32;
94
+ /** Built-in cap: the JSON state serialized size, in bytes. */
95
+ export declare const DECISION_MAX_BYTES = 65536;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The decision-provider wire types (the decision-providers spec): a
3
+ * provider TYPE whose models answer typed questions (noul / choice /
4
+ * score) over a JSON state, for extensions that need small structured
5
+ * judgments rather than a chat completion.
6
+ *
7
+ * TYPE-ONLY module, with NO imports at all: `JsonValue`/`JsonObject` are
8
+ * declared locally exactly the way `shared/src/json.ts` mirrors them, so
9
+ * the shared leaf keeps its zero-import invariant (enforced by
10
+ * test/views-purity.test.ts). The two `DECISION_MAX_*` constants are
11
+ * pure VALUES — wire modules carry "types + pure constants".
12
+ */
13
+ /** Built-in cap: at most this many questions per request. */
14
+ export const DECISION_MAX_QUESTIONS = 32;
15
+ /** Built-in cap: the JSON state serialized size, in bytes. */
16
+ export const DECISION_MAX_BYTES = 65_536;
@@ -54,6 +54,20 @@ export type InteractionRecord = {
54
54
  * model-asserted. Absent on the naive single-operator path (no user).
55
55
  */
56
56
  readonly answeredByUser?: string;
57
+ /**
58
+ * Decline affordance (ask-user Task 1, spec §5.2): `true` marks the
59
+ * record declinable — a human may skip it without answering (the
60
+ * decline route / `host.declineInteraction`). Absent or omitted means
61
+ * NOT declinable; `false` fails validation (the flag is `true`-only).
62
+ */
63
+ readonly declinable?: true;
64
+ /**
65
+ * How the record was declined (ask-user Task 1/2): "button" via the
66
+ * decline route or host method, "message" via an admitted decline
67
+ * message. Set only on a `skipped` decline; absent on every other
68
+ * terminal status.
69
+ */
70
+ readonly declinedVia?: "button" | "message";
57
71
  /**
58
72
  * The interaction body: a block tree (placement `interaction` — a
59
73
  * root `form` whose `submit` is the answer). Legacy-shaped bodies
@@ -163,6 +163,20 @@ export interface ServerSettings {
163
163
  readonly workerDeadlineMs?: number;
164
164
  readonly queueMax?: number;
165
165
  };
166
+ /**
167
+ * The published-hook call deadline (knowledge C7): the per-call cap,
168
+ * mirroring `SIGRUN_PUBLISHED_HOOK_DEADLINE` (env wins). Default
169
+ * 30000 ms.
170
+ */
171
+ readonly publishedHookDeadlineMs?: number;
172
+ /**
173
+ * The decision-provider knob (warden Task 6): `SIGRUN_DECISION_TIMEOUT_MS`
174
+ * — the per-call timeout (default AND cap, default 15000 ms), a
175
+ * positive integer.
176
+ */
177
+ readonly decisions?: {
178
+ readonly timeoutMs?: number;
179
+ };
166
180
  /** The multi-user execution jail (M1). `mode` and `osUsers` are restart-required. */
167
181
  readonly isolation?: {
168
182
  /** `SIGRUN_ISOLATION` — auto, always, or off; default auto. */
@@ -406,6 +420,15 @@ export interface SettingsFile {
406
420
  * next naming call without a restart.
407
421
  */
408
422
  readonly namingModel?: string | false;
423
+ /**
424
+ * Sigrun: the decision providers' default model (warden Task 6), a
425
+ * non-empty `provider/model-id` spec validated exactly like
426
+ * `defaultModel`. Absent = the first model of the first-registered
427
+ * decision provider. LIVE — the decision registry reads it per
428
+ * `host.decisions.evaluate` call, so a PATCH reaches the next call
429
+ * without a restart.
430
+ */
431
+ readonly decisionModel?: string;
409
432
  /**
410
433
  * Sigrun: the env-driven OpenAI-compatible provider's endpoint
411
434
  * (ruling 7), applied when `OPENAI_BASE_URL` is absent.
@@ -441,6 +464,15 @@ export interface SettingsFile {
441
464
  * `defaultTools`.
442
465
  */
443
466
  readonly autoEnableCodemode?: boolean;
467
+ /**
468
+ * Sigrun: the decision surface's toggles (warden Task 6). `tool`
469
+ * (default false) gates the core `decision_evaluate` tool — read at
470
+ * each tool call; a disabled tool returns an error result naming the
471
+ * setting. LIVE.
472
+ */
473
+ readonly decisions?: {
474
+ readonly tool?: boolean;
475
+ };
444
476
  /**
445
477
  * Sigrun: the static boot configuration (see {@link ServerSettings}).
446
478
  * Deployment defaults under env — changes require a restart.
@@ -339,6 +339,12 @@ export interface InteractionAnswered {
339
339
  readonly status: "answered";
340
340
  readonly answer: InteractionAnswer;
341
341
  }
342
+ /** The response of `POST .../interactions/:callId/decline`. */
343
+ export interface InteractionDeclined {
344
+ readonly callId: string;
345
+ readonly status: "skipped";
346
+ readonly declinedVia: "button";
347
+ }
342
348
  /** One ambient surface on the wire (envelope + body + details). */
343
349
  export type AmbientWire = AmbientRecord;
344
350
  /** The response of `GET /v1/conversations/:id/ambient`. */
@@ -607,6 +613,41 @@ export interface MetaResponse {
607
613
  readonly contributor: string;
608
614
  }[];
609
615
  };
616
+ /**
617
+ * The published-hook registry (knowledge C7): one row per OWNED hook
618
+ * name — `provider` is the owner slug, `shadowed` the losing
619
+ * providers (configured losers before built-in losers). Ownership is
620
+ * first-listed over `extensions[]`, configured before built-ins;
621
+ * empty when nothing published.
622
+ */
623
+ readonly publishedHooks: readonly {
624
+ readonly name: string;
625
+ readonly provider: string;
626
+ readonly shadowed: readonly string[];
627
+ }[];
628
+ /**
629
+ * The decision-provider seam projection (warden Task 7): the
630
+ * registry's providers (id/label/extension/models/status), the
631
+ * resolved default model (the live `decisionModel` setting, else the
632
+ * first model of the first-registered provider), and the boot-merge
633
+ * collision record. Empty arrays and no default on the no-provider
634
+ * boot.
635
+ */
636
+ readonly decisions: {
637
+ readonly providers: readonly {
638
+ readonly id: string;
639
+ readonly label?: string;
640
+ readonly extension: string;
641
+ readonly models: readonly string[];
642
+ readonly status: AuthStatus;
643
+ }[];
644
+ readonly default?: string;
645
+ readonly collisions: readonly {
646
+ readonly id: string;
647
+ readonly winner: string;
648
+ readonly losers: readonly string[];
649
+ }[];
650
+ };
610
651
  }
611
652
  /**
612
653
  * Provider auth readiness statuses (Task 6, spec §6.4 + provider plugins
@@ -633,6 +674,18 @@ export interface AuthCheckProvider {
633
674
  readonly status: AuthStatus;
634
675
  /** How many models the provider currently serves (best-effort count). */
635
676
  readonly models: number;
677
+ /**
678
+ * Warden Task 7: `"chat"` (a chat-catalog provider) or `"decision"` (a
679
+ * decision provider from the warden seam). Absent = `"chat"` (older
680
+ * servers never carried the field); a chat and a decision provider
681
+ * sharing an id stay SEPARATE entries, one of each kind.
682
+ */
683
+ readonly kind?: "chat" | "decision";
684
+ /**
685
+ * The decision provider's bare model ids (warden Task 7) — present
686
+ * only on `kind: "decision"` entries (`models` is their count).
687
+ */
688
+ readonly modelIds?: readonly string[];
636
689
  /** The owning contributor's name; absent for the env-degenerate provider. */
637
690
  readonly owner?: string;
638
691
  readonly detail?: string;
@@ -736,6 +789,11 @@ export interface ExtensionSurfaceInfo {
736
789
  * answer with `ops` and NO `data` — channels fall back to a full
737
790
  * fetch (ops folding is U1). Absent → per-instance data. */
738
791
  readonly sharedData?: boolean;
792
+ /** The panel opts in to per-conversation attachment: channels attach
793
+ * it with `params: { conversationId }` of the OPEN conversation. The
794
+ * id is client-supplied — the extension must check access. Absent
795
+ * (the sparse rule) → the panel is not conversation-scoped. */
796
+ readonly perConversation?: true;
739
797
  }
740
798
  /** The `GET /v1/extensions` payload (spec §7.7). */
741
799
  export interface ExtensionsResponse {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigrun/shared",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "sigrun shared leaf package \u2014 theme tokens, turn/conversation shaping, and the channel wire types (pure leaf, zero dependencies)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -72,6 +72,10 @@
72
72
  "types": "./dist/jobs.d.ts",
73
73
  "default": "./dist/jobs.js"
74
74
  },
75
+ "./decisions": {
76
+ "types": "./dist/decisions.d.ts",
77
+ "default": "./dist/decisions.js"
78
+ },
75
79
  "./blocks": {
76
80
  "types": "./dist/blocks/index.d.ts",
77
81
  "default": "./dist/blocks/index.js"
@@ -105,4 +109,4 @@
105
109
  "access": "public",
106
110
  "provenance": true
107
111
  }
108
- }
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigrun/sigrun",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Self-hosted, multi-user personal-assistant server",
5
5
  "type": "module",
6
6
  "exports": {
@@ -46,11 +46,11 @@
46
46
  "@fastify/static": "^10.1.7",
47
47
  "@sigrun/ext-codemode": "0.1.2",
48
48
  "@sigrun/ext-curator": "0.3.0",
49
- "@sigrun/ext-gateway": "0.1.16",
49
+ "@sigrun/ext-gateway": "0.1.18",
50
50
  "@sigrun/ext-mcp": "0.2.2",
51
51
  "@sigrun/ext-tool-search": "0.1.2",
52
52
  "@sigrun/oauth": "0.1.1",
53
- "@sigrun/shared": "0.8.0",
53
+ "@sigrun/shared": "0.9.0",
54
54
  "croner": "^10.0.1",
55
55
  "fastify": "^5.12.7",
56
56
  "semver": "^7.8.5",
@@ -61,9 +61,9 @@
61
61
  "@sigrun/shared"
62
62
  ],
63
63
  "optionalDependencies": {
64
- "@sigrun/sandbox-linux-arm64": "0.8.0",
65
- "@sigrun/sandbox-linux-x64": "0.8.0",
66
- "@sigrun/tui": "0.8.0"
64
+ "@sigrun/sandbox-linux-arm64": "0.9.0",
65
+ "@sigrun/sandbox-linux-x64": "0.9.0",
66
+ "@sigrun/tui": "0.9.0"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@sigrun/memory-markdown": "file:../sigrun-memory-markdown",