@sigrun/sigrun 0.7.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 (56) hide show
  1. package/CHANGELOG.md +78 -59
  2. package/README.md +32 -0
  3. package/dist/config.js +47 -0
  4. package/dist/curation/host.js +125 -21
  5. package/dist/curation/runs.js +15 -4
  6. package/dist/extension.d.ts +331 -4
  7. package/dist/harness/boot.js +114 -3
  8. package/dist/harness/complete.js +127 -0
  9. package/dist/harness/decision-tool.js +93 -0
  10. package/dist/harness/decisions.js +477 -0
  11. package/dist/harness/event-bus.js +12 -0
  12. package/dist/harness/interactions.js +81 -0
  13. package/dist/harness/interactive-tools.js +97 -0
  14. package/dist/harness/loader.js +285 -1
  15. package/dist/harness/packages.js +7 -0
  16. package/dist/harness/published-hooks.js +208 -0
  17. package/dist/harness/seam-host.js +149 -1
  18. package/dist/harness/settings.js +41 -0
  19. package/dist/history/git.js +6 -2
  20. package/dist/jobs/fire.js +10 -0
  21. package/dist/jobs/observe.js +27 -2
  22. package/dist/notices/events.js +8 -0
  23. package/dist/notices/service.js +297 -0
  24. package/dist/notices/store.js +331 -0
  25. package/dist/notices/validate.js +311 -0
  26. package/dist/server/conversation-ops.js +50 -0
  27. package/dist/server/core-views/skills-tree.js +3 -12
  28. package/dist/server/core-views/skills.js +11 -23
  29. package/dist/server/host-bridge.js +62 -1
  30. package/dist/server/index.js +26 -0
  31. package/dist/server/routes/events.js +20 -0
  32. package/dist/server/routes/extensions.js +5 -0
  33. package/dist/server/routes/interactions.js +48 -1
  34. package/dist/server/routes/meta.js +17 -0
  35. package/dist/server/routes/notices.js +94 -0
  36. package/dist/server/routes/providers.js +65 -8
  37. package/dist/server/routes/submissions.js +3 -0
  38. package/dist/server/routes/users.js +4 -0
  39. package/dist/server/surfaces/dedup.js +6 -0
  40. package/dist/server/visibility.js +30 -0
  41. package/dist/settings/registry.js +18 -0
  42. package/node_modules/@sigrun/shared/CHANGELOG.md +12 -0
  43. package/node_modules/@sigrun/shared/dist/decisions.d.ts +95 -0
  44. package/node_modules/@sigrun/shared/dist/decisions.js +16 -0
  45. package/node_modules/@sigrun/shared/dist/interactions.d.ts +14 -0
  46. package/node_modules/@sigrun/shared/dist/notices.d.ts +112 -0
  47. package/node_modules/@sigrun/shared/dist/notices.js +49 -0
  48. package/node_modules/@sigrun/shared/dist/settings.d.ts +32 -0
  49. package/node_modules/@sigrun/shared/dist/views.d.ts +63 -0
  50. package/node_modules/@sigrun/shared/package.json +9 -1
  51. package/package.json +7 -7
  52. package/webui/dist/assets/index-D5KowS7k.css +1 -0
  53. package/webui/dist/assets/index-DGIrLC6F.js +55 -0
  54. package/webui/dist/index.html +2 -2
  55. package/webui/dist/assets/index-BI5k-Oci.js +0 -55
  56. package/webui/dist/assets/index-EGluyRXk.css +0 -1
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `host.models.complete` (warden Task 4): one-shot completions for
3
+ * extensions — NO conversation, the auto-namer's `completeTitle` shape
4
+ * generalized into a host surface (skill-card generation is the first
5
+ * consumer).
6
+ *
7
+ * Model resolution (binding): an explicit `input.model` — ANY defined
8
+ * string, so a `\"\"` fails the parse rather than falling through — wins;
9
+ * else the live settings' `namingModel` when it is a string (`false`
10
+ * counts as unset); else the boot default model. The settings read is
11
+ * lazy (only when no explicit model). The chosen ref must be SERVED —
12
+ * an unserved model throws `UnservedModelError` naming it, never a
13
+ * silent fallback.
14
+ *
15
+ * Failure posture: throws on an unserved model, a timeout (default
16
+ * {@link DEFAULT_COMPLETE_TIMEOUT_MS} ms), a caller abort
17
+ * (`input.signal` — bound, not just forwarded: the race rejects even
18
+ * when the provider ignores its signal, and an already-aborted signal
19
+ * rejects before the request starts), or a `stopReason` of
20
+ * `error`/`aborted` — never resolves silently. The `Promise.race`, not
21
+ * the abort signal alone, bounds the wait: a provider that ignores the
22
+ * abort is abandoned without wedging the chain (the race keeps its
23
+ * late rejection handled).
24
+ */
25
+ import { parseModelSpec } from "../config.js";
26
+ import { requireServedModel } from "./providers/index.js";
27
+ /** Default bound on one one-shot completion. */
28
+ export const DEFAULT_COMPLETE_TIMEOUT_MS = 60_000;
29
+ /**
30
+ * Run ONE one-shot completion against the resolved model and return its
31
+ * concatenated text parts (non-text parts skipped) plus the
32
+ * `provider/id` of the model actually used.
33
+ *
34
+ * @throws `UnservedModelError` naming the ref when no contributor serves
35
+ * it; an `Error` naming the timeout when the call overruns
36
+ * `timeoutMs`; an `Error` carrying the message's `errorMessage` (or
37
+ * the stop reason) when the completion stops with `error`/`aborted`.
38
+ */
39
+ export async function completeOnce(deps, input) {
40
+ const ref = input.model !== undefined
41
+ ? parseModelSpec(input.model, "model")
42
+ : (() => {
43
+ // Lazy settings read: an explicit model never touches the file.
44
+ const settings = deps.readSettings();
45
+ return typeof settings.namingModel === "string"
46
+ ? parseModelSpec(settings.namingModel, "namingModel")
47
+ : deps.defaultModel;
48
+ })();
49
+ const model = requireServedModel(deps.models, ref);
50
+ const key = `${ref.provider}/${ref.modelId}`;
51
+ const timeoutMs = input.timeoutMs ?? DEFAULT_COMPLETE_TIMEOUT_MS;
52
+ const controller = new AbortController();
53
+ // A caller-side abort BOUNDS the wait: the race rejects promptly even
54
+ // when the provider ignores its signal (the request itself still gets
55
+ // the abort, best-effort cancellation of the underlying call). An
56
+ // already-aborted signal rejects before the request is started.
57
+ if (input.signal?.aborted) {
58
+ throw new Error("the completion was aborted by the caller");
59
+ }
60
+ let rejectCallerAbort;
61
+ const callerAborted = input.signal === undefined
62
+ ? undefined
63
+ : new Promise((_, reject) => {
64
+ rejectCallerAbort = reject;
65
+ });
66
+ const unlinkCaller = input.signal === undefined || rejectCallerAbort === undefined
67
+ ? undefined
68
+ : linkAbort(input.signal, controller, rejectCallerAbort);
69
+ let timer;
70
+ const timedOut = new Promise((_, reject) => {
71
+ timer = setTimeout(() => {
72
+ controller.abort();
73
+ reject(new Error(`the completion timed out after ${timeoutMs}ms`));
74
+ }, timeoutMs);
75
+ timer.unref?.();
76
+ });
77
+ let message;
78
+ try {
79
+ const request = deps.models.completeSimple(model, {
80
+ ...(input.systemPrompt === undefined
81
+ ? {}
82
+ : { systemPrompt: input.systemPrompt }),
83
+ messages: [
84
+ { role: "user", content: input.prompt, timestamp: Date.now() },
85
+ ],
86
+ }, { signal: controller.signal });
87
+ message = await Promise.race(callerAborted === undefined
88
+ ? [request, timedOut]
89
+ : [request, timedOut, callerAborted]);
90
+ }
91
+ finally {
92
+ if (timer !== undefined)
93
+ clearTimeout(timer);
94
+ unlinkCaller?.();
95
+ }
96
+ // An errored or aborted completion is a failure, not a result (the
97
+ // message may still carry text content alongside the error).
98
+ if (message.stopReason === "error" || message.stopReason === "aborted") {
99
+ throw new Error(`completion on ${key} stopped with ${message.stopReason}` +
100
+ (message.errorMessage === undefined
101
+ ? ""
102
+ : `: ${message.errorMessage}`));
103
+ }
104
+ return { text: textOf(message.content), model: key };
105
+ }
106
+ /** The text blocks of an assistant message, concatenated. */
107
+ function textOf(content) {
108
+ return content
109
+ .filter((block) => block.type === "text")
110
+ .map((block) => block.text)
111
+ .join("");
112
+ }
113
+ /**
114
+ * Abort `target` when `source` aborts and reject the race arm via
115
+ * `rejectRace` (the bounding arm: the race rejects even when the
116
+ * request ignores the abort). Callers must handle an already-aborted
117
+ * `source` BEFORE the request is started (completeOnce does). Returns
118
+ * the unlink.
119
+ */
120
+ function linkAbort(source, target, rejectRace) {
121
+ const fail = () => {
122
+ target.abort();
123
+ rejectRace(new Error("the completion was aborted by the caller"));
124
+ };
125
+ source.addEventListener("abort", fail, { once: true });
126
+ return () => source.removeEventListener("abort", fail);
127
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Warden Task 7: the core-owned `decision_evaluate` tool. The
3
+ * `sigrun.decisions` extension installs it when the boot-built registry
4
+ * lists ≥ 1 decision model — a no-provider boot carries no tool (the
5
+ * model is never offered a tool that can only refuse).
6
+ *
7
+ * Boot ordering: the registry builds AFTER the loader runs, while core
8
+ * extensions install BEFORE it. The extension therefore installs with
9
+ * an empty tool list (the placeholder), and boot RE-INSTALLS it with
10
+ * the tool once the registry exists (`registry.install` replaces a
11
+ * same-name extension in place) — the same late-fill seam the skills
12
+ * extension's collected-roots array uses, one level up.
13
+ *
14
+ * The execution reads `decisions.tool` from the LIVE settings per call
15
+ * (a settings write flips the tool without a restart) and evaluates
16
+ * through the boot-built registry with the core caller stamp `"sigrun"`.
17
+ * The result returns as JSON text for the model AND as
18
+ * `structuredOutput` under the declared schema (pi-durable rejects
19
+ * structuredOutput without one).
20
+ */
21
+ import { Type } from "@earendil-works/pi-ai";
22
+ import { defineExtension, defineTool } from "@earendil-works/pi-durable";
23
+ import { validateDecisionRequest } from "./decisions.js";
24
+ /** The core extension's reserved slug (boot.ts keeps the core slugs). */
25
+ export const DECISIONS_EXTENSION_NAME = "sigrun.decisions";
26
+ /** The tool's disabled refusal (the exact text the brief pins). */
27
+ export const DECISION_TOOL_DISABLED = "decision_evaluate is disabled; set decisions.tool to true";
28
+ function decisionError(message) {
29
+ return { output: [{ type: "text", text: message }], isError: true };
30
+ }
31
+ /**
32
+ * The `decision_evaluate` tool registration, built against the LIVE
33
+ * slots: the boot-built registry and the settings store (the read is
34
+ * per execution, so `decisions.tool` and `decisionModel` apply without
35
+ * a restart).
36
+ */
37
+ export function buildDecisionEvaluateTool(deps) {
38
+ return defineTool({
39
+ name: "decision_evaluate",
40
+ description: "Small structured judgments over a state: one judgment per question, answered by a " +
41
+ "dedicated decision model. Every question is a `noul` (a probability), a `choice` over " +
42
+ "labels, or a `score` over ordered levels — include a no-match option (for example " +
43
+ "\"none of the above\") in every choice so the judge is never forced.",
44
+ parameters: Type.Object({
45
+ state: Type.Union([Type.String(), Type.Unknown()], {
46
+ description: "The state the judgments run over (a string or any strict-JSON value).",
47
+ }),
48
+ questions: Type.Record(Type.String(), Type.Unknown(), {
49
+ description: "Question key → the question ({type, instructions?, criteria}).",
50
+ }),
51
+ model: Type.Optional(Type.String({
52
+ description: "A decision model as `provider/model`; the default resolves from settings.",
53
+ })),
54
+ }),
55
+ structuredOutputSchema: Type.Object({ ok: Type.Boolean() }, { additionalProperties: true }),
56
+ async execute(args) {
57
+ const enabled = deps.settings.read().decisions?.tool === true;
58
+ if (!enabled) {
59
+ return decisionError(DECISION_TOOL_DISABLED);
60
+ }
61
+ // Validate the shape BEFORE the registry (the registry validates
62
+ // again against the resolved model's limits — this keeps the
63
+ // refusal vocabulary identical for every caller).
64
+ const validation = validateDecisionRequest(args);
65
+ const model = typeof args.model === "string" ? args.model : undefined;
66
+ const result = validation.ok
67
+ ? await deps.decisions.evaluate("sigrun", validation.request, {
68
+ ...(model === undefined ? {} : { model }),
69
+ })
70
+ : {
71
+ ok: false,
72
+ code: validation.code,
73
+ error: `${validation.code} at ${validation.errors[0]?.path ?? ""}: ${validation.errors[0]?.message ?? "invalid"}`,
74
+ };
75
+ return {
76
+ output: [{ type: "text", text: JSON.stringify(result) }],
77
+ structuredOutput: result,
78
+ };
79
+ },
80
+ });
81
+ }
82
+ /**
83
+ * The `sigrun.decisions` core extension. `tools` empty = the boot-time
84
+ * placeholder (a no-provider boot KEEPS the placeholder — no tool);
85
+ * boot re-installs with `buildDecisionEvaluateTool` once the registry
86
+ * lists ≥ 1 decision model.
87
+ */
88
+ export function createDecisionsExtension(tools = []) {
89
+ return defineExtension({
90
+ name: DECISIONS_EXTENSION_NAME,
91
+ tools: [...tools],
92
+ });
93
+ }