@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.
- package/CHANGELOG.md +78 -59
- package/README.md +32 -0
- 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 +331 -4
- package/dist/harness/boot.js +114 -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 +285 -1
- package/dist/harness/packages.js +7 -0
- package/dist/harness/published-hooks.js +208 -0
- package/dist/harness/seam-host.js +149 -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/observe.js +27 -2
- package/dist/notices/events.js +8 -0
- package/dist/notices/service.js +297 -0
- package/dist/notices/store.js +331 -0
- package/dist/notices/validate.js +311 -0
- package/dist/server/conversation-ops.js +50 -0
- 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 +62 -1
- package/dist/server/index.js +26 -0
- package/dist/server/routes/events.js +20 -0
- 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/notices.js +94 -0
- package/dist/server/routes/providers.js +65 -8
- package/dist/server/routes/submissions.js +3 -0
- package/dist/server/routes/users.js +4 -0
- package/dist/server/surfaces/dedup.js +6 -0
- package/dist/server/visibility.js +30 -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/notices.d.ts +112 -0
- package/node_modules/@sigrun/shared/dist/notices.js +49 -0
- package/node_modules/@sigrun/shared/dist/settings.d.ts +32 -0
- package/node_modules/@sigrun/shared/dist/views.d.ts +63 -0
- package/node_modules/@sigrun/shared/package.json +9 -1
- package/package.json +7 -7
- package/webui/dist/assets/index-D5KowS7k.css +1 -0
- package/webui/dist/assets/index-DGIrLC6F.js +55 -0
- package/webui/dist/index.html +2 -2
- package/webui/dist/assets/index-BI5k-Oci.js +0 -55
- 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
|
+
}
|