@sigrun/sigrun 0.8.0 → 0.10.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/CHANGELOG.md +55 -87
- package/dist/config.js +47 -0
- package/dist/curation/host.js +125 -21
- package/dist/curation/runs.js +15 -4
- package/dist/extension.d.ts +268 -3
- package/dist/harness/boot.js +113 -3
- package/dist/harness/complete.js +127 -0
- package/dist/harness/decision-tool.js +93 -0
- package/dist/harness/decisions.js +477 -0
- package/dist/harness/event-bus.js +12 -0
- package/dist/harness/interactions.js +81 -0
- package/dist/harness/interactive-tools.js +97 -0
- package/dist/harness/loader.js +243 -1
- package/dist/harness/packages.js +7 -0
- package/dist/harness/published-hooks.js +208 -0
- package/dist/harness/seam-host.js +136 -1
- package/dist/harness/settings.js +41 -0
- package/dist/history/git.js +6 -2
- package/dist/jobs/fire.js +10 -0
- package/dist/jobs/index.js +3 -0
- package/dist/jobs/observe.js +27 -2
- package/dist/jobs/spec.js +15 -1
- package/dist/server/conversation-ops.js +31 -0
- package/dist/server/core-views/jobs-form.js +335 -0
- package/dist/server/core-views/jobs.js +300 -15
- package/dist/server/core-views/skills-tree.js +3 -12
- package/dist/server/core-views/skills.js +11 -23
- package/dist/server/host-bridge.js +53 -1
- package/dist/server/index.js +2 -1
- package/dist/server/routes/extensions.js +5 -0
- package/dist/server/routes/interactions.js +48 -1
- package/dist/server/routes/meta.js +17 -0
- package/dist/server/routes/providers.js +65 -8
- package/dist/server/routes/submissions.js +3 -0
- package/dist/settings/registry.js +18 -0
- package/node_modules/@sigrun/shared/CHANGELOG.md +12 -0
- package/node_modules/@sigrun/shared/dist/decisions.d.ts +95 -0
- package/node_modules/@sigrun/shared/dist/decisions.js +16 -0
- package/node_modules/@sigrun/shared/dist/interactions.d.ts +14 -0
- package/node_modules/@sigrun/shared/dist/jobs.d.ts +4 -0
- package/node_modules/@sigrun/shared/dist/settings.d.ts +32 -0
- package/node_modules/@sigrun/shared/dist/views.d.ts +58 -0
- package/node_modules/@sigrun/shared/package.json +6 -2
- package/package.json +8 -8
- package/webui/dist/assets/index-By63QKOA.js +55 -0
- package/webui/dist/assets/index-D5KowS7k.css +1 -0
- package/webui/dist/index.html +2 -2
- package/webui/dist/assets/index-B3uMNF59.css +0 -1
- package/webui/dist/assets/index-BXsCyod4.js +0 -55
|
@@ -88,6 +88,11 @@ export function registerExtensionRoutes(app, ctx) {
|
|
|
88
88
|
...(def.sharedData === true
|
|
89
89
|
? { sharedData: true }
|
|
90
90
|
: {}),
|
|
91
|
+
// The per-conversation opt-in (sparse: present only
|
|
92
|
+
// when the panel opted in).
|
|
93
|
+
...(def.perConversation === true
|
|
94
|
+
? { perConversation: true }
|
|
95
|
+
: {}),
|
|
91
96
|
})),
|
|
92
97
|
}),
|
|
93
98
|
};
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*/
|
|
35
35
|
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
36
36
|
import { AmbientDoc, AmbientOwnersDoc, asAmbientRecord, } from "../../harness/ambient.js";
|
|
37
|
-
import { InteractionDoc, InteractionsIndexDoc, answerFromBody, applyAnswer, asInteractionRecord, indexSettled, maskedAnswerValues, } from "../../harness/interactions.js";
|
|
37
|
+
import { InteractionDoc, InteractionsIndexDoc, answerFromBody, applyAnswer, applyDecline, asInteractionRecord, indexSettled, maskedAnswerValues, sweepSecretDelivery, } from "../../harness/interactions.js";
|
|
38
38
|
import { loadConversation } from "../conversation-handler.js";
|
|
39
39
|
import { ID_PARAMS_SCHEMA, conversationId } from "../helpers.js";
|
|
40
40
|
import { accessOf } from "@sigrun/shared/access";
|
|
@@ -221,6 +221,53 @@ export function registerInteractionRoutes(app, ctx) {
|
|
|
221
221
|
answer: committed.answer,
|
|
222
222
|
};
|
|
223
223
|
});
|
|
224
|
+
// The decline route (ask-user Task 1): the answer route's peer — a
|
|
225
|
+
// human declines a DECLINABLE record (status "skipped", declinedVia
|
|
226
|
+
// "button"). Same access (write), same commit structure, same
|
|
227
|
+
// indexSettled + reply-after-commit; every successful decline sweeps
|
|
228
|
+
// the record's unread secret delivery inside the commit (the
|
|
229
|
+
// settleInteraction pattern). 404 unknown / 404 not-pending /
|
|
230
|
+
// 409 not-declinable.
|
|
231
|
+
app.post("/v1/conversations/:id/interactions/:callId/decline", {
|
|
232
|
+
schema: { params: ANSWER_PARAMS_SCHEMA },
|
|
233
|
+
preHandler: loadConversation(ctx, "write"),
|
|
234
|
+
}, async (request, reply) => {
|
|
235
|
+
const { callId } = request.params;
|
|
236
|
+
const conversation = request.conversation;
|
|
237
|
+
const committed = await conversation.commit(async (tx) => {
|
|
238
|
+
const draft = await tx.doc(InteractionDoc, conversation.id, callId, callId);
|
|
239
|
+
// Unknown callId: the doc materializes empty — kind "" never
|
|
240
|
+
// seeded (the answer route's detection).
|
|
241
|
+
if (draft.kind === "") {
|
|
242
|
+
return { found: false };
|
|
243
|
+
}
|
|
244
|
+
const outcome = applyDecline(draft, "button", Date.now(), request.principal.user);
|
|
245
|
+
if (outcome !== "declined") {
|
|
246
|
+
// A loser of the first-wins race never sweeps: the delivery
|
|
247
|
+
// (when there is one) belongs to the winner (the answer
|
|
248
|
+
// route's rule).
|
|
249
|
+
return { found: true, outcome };
|
|
250
|
+
}
|
|
251
|
+
const index = await tx.doc(InteractionsIndexDoc);
|
|
252
|
+
indexSettled(index, conversation.id, callId);
|
|
253
|
+
sweepSecretDelivery(conversation.id, callId);
|
|
254
|
+
return { found: true, outcome };
|
|
255
|
+
}, BACKGROUND_CONTEXT);
|
|
256
|
+
if (!committed.found) {
|
|
257
|
+
return await reply.code(404).send({ error: "no_such_interaction" });
|
|
258
|
+
}
|
|
259
|
+
if (committed.outcome === "not_pending") {
|
|
260
|
+
return await reply.code(404).send({ error: "not_pending" });
|
|
261
|
+
}
|
|
262
|
+
if (committed.outcome === "not_declinable") {
|
|
263
|
+
return await reply.code(409).send({ error: "not_declinable" });
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
callId,
|
|
267
|
+
status: "skipped",
|
|
268
|
+
declinedVia: "button",
|
|
269
|
+
};
|
|
270
|
+
});
|
|
224
271
|
// ─── Ambient seam ────────────────────────────────────────────────────
|
|
225
272
|
// The conversation's ambient surfaces, replacement-state reads.
|
|
226
273
|
app.get("/v1/conversations/:id/ambient", { schema: { params: ID_PARAMS_SCHEMA } }, async (request, reply) => {
|
|
@@ -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 (!
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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 (!
|
|
224
|
+
if (!isRoutableProvider(id)) {
|
|
169
225
|
return await reply.code(404).send({ error: "provider_not_found" });
|
|
170
226
|
}
|
|
171
|
-
|
|
172
|
-
await opened.
|
|
173
|
-
|
|
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,18 @@ 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.10.0] - 2026-11-06
|
|
10
|
+
|
|
11
|
+
- `JobSpec.description?: string` (and the flattened `JobWire.description`): the optional,
|
|
12
|
+
trimmed, ≤500-char human description shown in the Jobs view.
|
|
13
|
+
|
|
14
|
+
## [0.9.0] - 2026-11-05
|
|
15
|
+
|
|
16
|
+
- `InteractionRecord` gains optional `declinable: true` and settled `declinedVia:
|
|
17
|
+
"button" | "message"` (the ask-user decline wire surface — additive, optional).
|
|
18
|
+
- `InteractionDeclined` wire type beside `InteractionAnswered` (the decline route's
|
|
19
|
+
200 body).
|
|
20
|
+
|
|
9
21
|
## [0.8.0] - 2026-11-04
|
|
10
22
|
|
|
11
23
|
- 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
|
|
@@ -53,6 +53,8 @@ export type JobAction = {
|
|
|
53
53
|
/** A job's declared shape — what every source (api/file/extension) supplies. */
|
|
54
54
|
export type JobSpec = {
|
|
55
55
|
name: string;
|
|
56
|
+
/** Optional ≤500-char human description, shown in the Jobs view. */
|
|
57
|
+
description?: string;
|
|
56
58
|
schedule: JobSchedule;
|
|
57
59
|
action: JobAction;
|
|
58
60
|
overlap?: "skip" | "queue";
|
|
@@ -68,6 +70,8 @@ export type JobWire = {
|
|
|
68
70
|
source: "api" | "file" | "extension";
|
|
69
71
|
owner?: string;
|
|
70
72
|
sharing?: Sharing;
|
|
73
|
+
/** Optional ≤500-char human description, shown in the Jobs view. */
|
|
74
|
+
description?: string;
|
|
71
75
|
schedule: JobSchedule;
|
|
72
76
|
action: JobAction;
|
|
73
77
|
enabled: boolean;
|
|
@@ -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.
|
|
3
|
+
"version": "0.10.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.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Self-hosted, multi-user personal-assistant server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"@earendil-works/pi-tui": "^1.3.0",
|
|
46
46
|
"@fastify/static": "^10.1.7",
|
|
47
47
|
"@sigrun/ext-codemode": "0.1.2",
|
|
48
|
-
"@sigrun/ext-curator": "0.3.
|
|
49
|
-
"@sigrun/ext-gateway": "0.1.
|
|
50
|
-
"@sigrun/ext-mcp": "0.2.
|
|
48
|
+
"@sigrun/ext-curator": "0.3.2",
|
|
49
|
+
"@sigrun/ext-gateway": "0.1.19",
|
|
50
|
+
"@sigrun/ext-mcp": "0.2.3",
|
|
51
51
|
"@sigrun/ext-tool-search": "0.1.2",
|
|
52
52
|
"@sigrun/oauth": "0.1.1",
|
|
53
|
-
"@sigrun/shared": "0.
|
|
53
|
+
"@sigrun/shared": "0.10.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.
|
|
65
|
-
"@sigrun/sandbox-linux-x64": "0.
|
|
66
|
-
"@sigrun/tui": "0.
|
|
64
|
+
"@sigrun/sandbox-linux-arm64": "0.10.0",
|
|
65
|
+
"@sigrun/sandbox-linux-x64": "0.10.0",
|
|
66
|
+
"@sigrun/tui": "0.10.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@sigrun/memory-markdown": "file:../sigrun-memory-markdown",
|