@sigrun/sigrun 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/CHANGELOG.md +204 -0
- package/CONTEXT.md +163 -0
- package/LICENSE +21 -0
- package/README.md +691 -2
- package/THIRD_PARTY_NOTICES.md +28 -0
- package/dist/backup/marker.js +80 -0
- package/dist/backup/run-backup.js +339 -0
- package/dist/cli.js +952 -0
- package/dist/commands/expand.js +245 -0
- package/dist/commands/templates.js +272 -0
- package/dist/config.js +1146 -0
- package/dist/extension.d.ts +431 -0
- package/dist/harness/activity.js +42 -0
- package/dist/harness/ambient.js +261 -0
- package/dist/harness/auth.js +130 -0
- package/dist/harness/boot.js +865 -0
- package/dist/harness/builtins.js +191 -0
- package/dist/harness/event-bus.js +1203 -0
- package/dist/harness/health.js +30 -0
- package/dist/harness/identity/index.js +37 -0
- package/dist/harness/identity/invites.js +94 -0
- package/dist/harness/identity/json-store.js +108 -0
- package/dist/harness/identity/setup-code.js +81 -0
- package/dist/harness/identity/tokens.js +101 -0
- package/dist/harness/identity/users.js +93 -0
- package/dist/harness/interactions.js +283 -0
- package/dist/harness/loader.js +1236 -0
- package/dist/harness/memory-default.js +35 -0
- package/dist/harness/naming.js +193 -0
- package/dist/harness/packages.js +1002 -0
- package/dist/harness/providers/auth-bridge.js +60 -0
- package/dist/harness/providers/index.js +277 -0
- package/dist/harness/providers/models-store.js +105 -0
- package/dist/harness/providers/refresh.js +161 -0
- package/dist/harness/providers/scheduler.js +89 -0
- package/dist/harness/registry.js +9 -0
- package/dist/harness/reserved-sections.js +24 -0
- package/dist/harness/seam-host.js +239 -0
- package/dist/harness/sections/context-files.js +78 -0
- package/dist/harness/sections/model-guidance.js +77 -0
- package/dist/harness/settings.js +1053 -0
- package/dist/harness/stores.js +450 -0
- package/dist/harness/tools.js +500 -0
- package/dist/harness/workspace.js +73 -0
- package/dist/index.js +39 -0
- package/dist/jobs/docs.js +257 -0
- package/dist/jobs/fire.js +342 -0
- package/dist/jobs/index.js +228 -0
- package/dist/jobs/observe.js +165 -0
- package/dist/jobs/reconcile.js +245 -0
- package/dist/jobs/schedule.js +77 -0
- package/dist/jobs/spec.js +248 -0
- package/dist/jobs/task.js +190 -0
- package/dist/jobs/transfer.js +53 -0
- package/dist/serve.js +164 -0
- package/dist/server/auth/claim-lock.js +28 -0
- package/dist/server/auth/ip.js +51 -0
- package/dist/server/auth/login-limiter.js +52 -0
- package/dist/server/auth/loopback.js +47 -0
- package/dist/server/auth/pipeline.js +330 -0
- package/dist/server/auth/principal.js +26 -0
- package/dist/server/commands.js +198 -0
- package/dist/server/content-parts.js +142 -0
- package/dist/server/context.js +13 -0
- package/dist/server/conversation-handler.js +61 -0
- package/dist/server/conversation-ops.js +310 -0
- package/dist/server/events/hub.js +39 -0
- package/dist/server/events/types.js +8 -0
- package/dist/server/helpers.js +351 -0
- package/dist/server/host-bridge.js +131 -0
- package/dist/server/index.js +232 -0
- package/dist/server/notifications.js +185 -0
- package/dist/server/ownership.js +67 -0
- package/dist/server/readiness.js +36 -0
- package/dist/server/routes/auth.js +172 -0
- package/dist/server/routes/conversations.js +341 -0
- package/dist/server/routes/events.js +161 -0
- package/dist/server/routes/extension-routes.js +127 -0
- package/dist/server/routes/extensions.js +168 -0
- package/dist/server/routes/files.js +308 -0
- package/dist/server/routes/interactions.js +260 -0
- package/dist/server/routes/jobs.js +271 -0
- package/dist/server/routes/meta.js +146 -0
- package/dist/server/routes/metrics.js +219 -0
- package/dist/server/routes/onboarding.js +268 -0
- package/dist/server/routes/probes.js +53 -0
- package/dist/server/routes/providers.js +198 -0
- package/dist/server/routes/settings.js +48 -0
- package/dist/server/routes/stream.js +131 -0
- package/dist/server/routes/submissions.js +317 -0
- package/dist/server/routes/users.js +169 -0
- package/dist/server/schema-fixture.js +950 -0
- package/dist/server/session-meta.js +247 -0
- package/dist/server/static.js +169 -0
- package/dist/server/views.js +71 -0
- package/dist/skills/index.js +418 -0
- package/node_modules/@sigrun/shared/CHANGELOG.md +16 -0
- package/node_modules/@sigrun/shared/LICENSE +21 -0
- package/node_modules/@sigrun/shared/README.md +10 -0
- package/node_modules/@sigrun/shared/dist/access.d.ts +55 -0
- package/node_modules/@sigrun/shared/dist/access.js +72 -0
- package/node_modules/@sigrun/shared/dist/ambient.d.ts +111 -0
- package/node_modules/@sigrun/shared/dist/ambient.js +23 -0
- package/node_modules/@sigrun/shared/dist/commands.d.ts +75 -0
- package/node_modules/@sigrun/shared/dist/commands.js +25 -0
- package/node_modules/@sigrun/shared/dist/conversations.d.ts +31 -0
- package/node_modules/@sigrun/shared/dist/conversations.js +94 -0
- package/node_modules/@sigrun/shared/dist/interactions.d.ts +91 -0
- package/node_modules/@sigrun/shared/dist/interactions.js +21 -0
- package/node_modules/@sigrun/shared/dist/jobs.d.ts +100 -0
- package/node_modules/@sigrun/shared/dist/jobs.js +19 -0
- package/node_modules/@sigrun/shared/dist/json.d.ts +20 -0
- package/node_modules/@sigrun/shared/dist/json.js +13 -0
- package/node_modules/@sigrun/shared/dist/session-meta.d.ts +103 -0
- package/node_modules/@sigrun/shared/dist/session-meta.js +13 -0
- package/node_modules/@sigrun/shared/dist/settings.d.ts +401 -0
- package/node_modules/@sigrun/shared/dist/settings.js +19 -0
- package/node_modules/@sigrun/shared/dist/themes.d.ts +50 -0
- package/node_modules/@sigrun/shared/dist/themes.js +156 -0
- package/node_modules/@sigrun/shared/dist/turns.d.ts +102 -0
- package/node_modules/@sigrun/shared/dist/turns.js +166 -0
- package/node_modules/@sigrun/shared/dist/view-delta.d.ts +54 -0
- package/node_modules/@sigrun/shared/dist/view-delta.js +135 -0
- package/node_modules/@sigrun/shared/dist/views.d.ts +742 -0
- package/node_modules/@sigrun/shared/dist/views.js +38 -0
- package/node_modules/@sigrun/shared/package.json +83 -0
- package/package.json +90 -5
- package/webui/dist/assets/index-Rl0KhCLa.css +1 -0
- package/webui/dist/assets/index-rOiARZLF.js +50 -0
- package/webui/dist/index.html +37 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The no-op memory default (spec §3, extension-separation ruling).
|
|
3
|
+
*
|
|
4
|
+
* Bare sigrun core (zero extensions) boots a NO-OP memory surface: the same
|
|
5
|
+
* stable `long-term-memory` placeholder section the real degenerate backend
|
|
6
|
+
* renders (`"long-term memory backend not configured"` — the string
|
|
7
|
+
* the memory extension's `MEMORY_PLACEHOLDER` defines), no recall tool, no
|
|
8
|
+
* hooks. Memory features appear only when a memory extension is installed;
|
|
9
|
+
* until then every conversation still sees a stable prompt surface — the
|
|
10
|
+
* zero-extensions guarantee holds via no-op defaults, not bundled
|
|
11
|
+
* degenerate implementations.
|
|
12
|
+
*
|
|
13
|
+
* The section is intentionally NOT gated on the real package's export: the
|
|
14
|
+
* string is part of the prompt contract, duplicated here so core has no
|
|
15
|
+
* dependency on any extension package.
|
|
16
|
+
*/
|
|
17
|
+
import { defineExtension, section } from "@earendil-works/pi-durable";
|
|
18
|
+
/** The section key the memory surface owns (real extension shares it). */
|
|
19
|
+
export const MEMORY_SECTION_KEY = "long-term-memory";
|
|
20
|
+
/**
|
|
21
|
+
* The stable placeholder text — byte-identical to the memory extension's
|
|
22
|
+
* `MEMORY_PLACEHOLDER` ("long-term memory backend not configured").
|
|
23
|
+
*/
|
|
24
|
+
export const MEMORY_PLACEHOLDER = "long-term memory backend not configured";
|
|
25
|
+
/**
|
|
26
|
+
* The no-op memory extension: one placeholder section, nothing else.
|
|
27
|
+
* The render reads nothing from the input, so it is stable for any
|
|
28
|
+
* `PromptInput`.
|
|
29
|
+
*/
|
|
30
|
+
export const noopMemoryExtension = defineExtension({
|
|
31
|
+
name: "sigrun.memory-noop",
|
|
32
|
+
sections: [
|
|
33
|
+
section(MEMORY_SECTION_KEY, () => MEMORY_PLACEHOLDER),
|
|
34
|
+
],
|
|
35
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic conversation naming (UI refresh Task A2).
|
|
3
|
+
*
|
|
4
|
+
* One background one-shot pi-ai completion after a conversation's first
|
|
5
|
+
* settled ANSWERED run writes `SessionMeta.autoName` — the sidebar's
|
|
6
|
+
* fallback display name, always under a user-set `name`. The call is NOT
|
|
7
|
+
* a pi-durable conversation: it goes straight through `Models
|
|
8
|
+
* .completeSimple` with the system prompt and a user message carrying the
|
|
9
|
+
* run's first input and its answer, thinking off (no `reasoning` option),
|
|
10
|
+
* bounded by an abort signal fired after `timeoutMs`.
|
|
11
|
+
*
|
|
12
|
+
* Naming must never hurt a run (the plan's review focus 1): every skip
|
|
13
|
+
* check runs before the model call, every failure (throw, timeout, an
|
|
14
|
+
* uncleanable response) commits one `autoNameAttempts` bump and logs one
|
|
15
|
+
* line, nothing ever rethrows into the event bus, and attempts stop at
|
|
16
|
+
* {@link NAMING_MAX_ATTEMPTS}. Task-owned conversations (subagent
|
|
17
|
+
* transcripts — `ConversationRecord.owner` set) are never named, and
|
|
18
|
+
* `settings.namingModel: false` disables the feature outright.
|
|
19
|
+
*/
|
|
20
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
21
|
+
import { parseModelSpec } from "../config.js";
|
|
22
|
+
import { SessionMetaDoc } from "../server/session-meta.js";
|
|
23
|
+
import { requireServedModel } from "./providers/index.js";
|
|
24
|
+
/** The one-shot naming prompt (the plan fixes it verbatim). */
|
|
25
|
+
export const NAMING_SYSTEM_PROMPT = "Write a short title (at most 6 words) for this conversation. Reply with the title only: no quotes, no trailing punctuation.";
|
|
26
|
+
/** After this many failed naming attempts a conversation stays unnamed. */
|
|
27
|
+
export const NAMING_MAX_ATTEMPTS = 3;
|
|
28
|
+
/** Default bound on one naming completion. */
|
|
29
|
+
const DEFAULT_TIMEOUT_MS = 20_000;
|
|
30
|
+
/** Each transcript half of the naming prompt is capped at this length. */
|
|
31
|
+
const PROMPT_TEXT_LIMIT = 2_000;
|
|
32
|
+
/** The cleaned title's character cap (applied on a word boundary). */
|
|
33
|
+
const TITLE_CHAR_LIMIT = 60;
|
|
34
|
+
/** The cleaned title's word cap. */
|
|
35
|
+
const TITLE_WORD_LIMIT = 6;
|
|
36
|
+
/**
|
|
37
|
+
* Scrub a naming response into a display title: trim; drop surrounding
|
|
38
|
+
* quotes/backticks (the pair must match — an inner apostrophe never
|
|
39
|
+
* matches) and a leading `Title:` label; collapse whitespace and newlines
|
|
40
|
+
* to single spaces; strip trailing sentence punctuation; keep the first
|
|
41
|
+
* {@link TITLE_WORD_LIMIT} words; cap at {@link TITLE_CHAR_LIMIT}
|
|
42
|
+
* characters on a word boundary (a single longer word is cut at the cap).
|
|
43
|
+
* `undefined` when nothing remains.
|
|
44
|
+
*/
|
|
45
|
+
export function cleanTitle(raw) {
|
|
46
|
+
let text = raw.trim();
|
|
47
|
+
for (;;) {
|
|
48
|
+
const quoted = /^(['"`])([\s\S]*)\1$/.exec(text);
|
|
49
|
+
if (quoted === null)
|
|
50
|
+
break;
|
|
51
|
+
text = quoted[2].trim();
|
|
52
|
+
}
|
|
53
|
+
text = text.replace(/^title:\s*/i, "");
|
|
54
|
+
text = text.replace(/\s+/g, " ").trim();
|
|
55
|
+
text = text.replace(/[.,;:!?]+$/, "").trim();
|
|
56
|
+
if (text === "")
|
|
57
|
+
return undefined;
|
|
58
|
+
text = text.split(" ").slice(0, TITLE_WORD_LIMIT).join(" ");
|
|
59
|
+
if (text.length > TITLE_CHAR_LIMIT) {
|
|
60
|
+
const slice = text.slice(0, TITLE_CHAR_LIMIT);
|
|
61
|
+
const boundary = slice.lastIndexOf(" ");
|
|
62
|
+
text = boundary > 0 ? slice.slice(0, boundary) : slice;
|
|
63
|
+
}
|
|
64
|
+
return text === "" ? undefined : text;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create the auto-namer. `runSettled` returns immediately — naming runs in
|
|
68
|
+
* the background on a private chain, one call at a time — so the event
|
|
69
|
+
* bus's hook deadline never bounds a naming completion. `idle()` resolves
|
|
70
|
+
* when the chain is drained (tests await it; boot never does).
|
|
71
|
+
*/
|
|
72
|
+
export function createAutoNamer(deps) {
|
|
73
|
+
const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
74
|
+
const log = deps.log ?? ((message) => console.error(message));
|
|
75
|
+
// The serialized naming chain: each event names after the previous one
|
|
76
|
+
// finished. nameConversation never rejects, so the chain never breaks.
|
|
77
|
+
let tail = Promise.resolve();
|
|
78
|
+
const runSettled = (event) => {
|
|
79
|
+
tail = tail.then(() => nameConversation(event));
|
|
80
|
+
};
|
|
81
|
+
return { runSettled, idle: () => tail };
|
|
82
|
+
/** Name one settled run's conversation; never rethrows. */
|
|
83
|
+
async function nameConversation(event) {
|
|
84
|
+
const id = event.conversationId;
|
|
85
|
+
try {
|
|
86
|
+
if (event.outcome.status !== "answered")
|
|
87
|
+
return;
|
|
88
|
+
if (deps.readSettings().namingModel === false)
|
|
89
|
+
return;
|
|
90
|
+
if (await deps.isTaskOwned(id))
|
|
91
|
+
return;
|
|
92
|
+
const conversation = await deps.harness.conversation(id, BACKGROUND_CONTEXT);
|
|
93
|
+
if (conversation === undefined)
|
|
94
|
+
return;
|
|
95
|
+
const meta = await deps.harness.snapshot(SessionMetaDoc, id, BACKGROUND_CONTEXT);
|
|
96
|
+
// Cron-run conversations (jobs Task 2) are never named: their
|
|
97
|
+
// server-stamped name ("<job> · <local date>") is the display
|
|
98
|
+
// name, and a model-written autoName would churn under it. The
|
|
99
|
+
// skip sits next to the isTaskOwned check per the plan.
|
|
100
|
+
if (meta?.source === "cron")
|
|
101
|
+
return;
|
|
102
|
+
// An absent doc (never written — reads serve nothing, defaults are
|
|
103
|
+
// free) is an unnamed conversation with zero attempts: name it.
|
|
104
|
+
if (meta?.name !== undefined || meta?.autoName !== undefined)
|
|
105
|
+
return;
|
|
106
|
+
if ((meta?.autoNameAttempts ?? 0) >= NAMING_MAX_ATTEMPTS)
|
|
107
|
+
return;
|
|
108
|
+
const settings = deps.readSettings();
|
|
109
|
+
const ref = typeof settings.namingModel === "string"
|
|
110
|
+
? parseModelSpec(settings.namingModel, "namingModel")
|
|
111
|
+
: deps.defaultModel;
|
|
112
|
+
const model = requireServedModel(deps.models, ref);
|
|
113
|
+
const title = await completeTitle(model, event.outcome.answer.content, event.inputTexts[0] ?? "");
|
|
114
|
+
if (title === undefined) {
|
|
115
|
+
await recordFailure(id, "the model returned no usable title");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Commit only while both name fields are still absent — a user
|
|
119
|
+
// naming the conversation mid-call wins.
|
|
120
|
+
await conversation.commit(async (tx) => {
|
|
121
|
+
const draft = await tx.doc(SessionMetaDoc, id);
|
|
122
|
+
if (draft.name !== undefined || draft.autoName !== undefined)
|
|
123
|
+
return;
|
|
124
|
+
draft.autoName = title;
|
|
125
|
+
}, BACKGROUND_CONTEXT);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
try {
|
|
129
|
+
await recordFailure(id, error instanceof Error ? error.message : String(error));
|
|
130
|
+
}
|
|
131
|
+
catch (commitError) {
|
|
132
|
+
log(`auto-name: conversation ${String(id)} could not record a failed attempt: ${commitError instanceof Error ? commitError.message : String(commitError)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Run the one-shot completion and clean its text; `undefined` = unusable. */
|
|
137
|
+
async function completeTitle(model, answerContent, inputText) {
|
|
138
|
+
const prompt = `User: ${inputText.slice(0, PROMPT_TEXT_LIMIT)}\n\n` +
|
|
139
|
+
`Assistant: ${textOf(answerContent).slice(0, PROMPT_TEXT_LIMIT)}`;
|
|
140
|
+
const controller = new AbortController();
|
|
141
|
+
let timer;
|
|
142
|
+
const timedOut = new Promise((_, reject) => {
|
|
143
|
+
timer = setTimeout(() => {
|
|
144
|
+
controller.abort();
|
|
145
|
+
reject(new Error(`the naming call timed out after ${timeoutMs}ms`));
|
|
146
|
+
}, timeoutMs);
|
|
147
|
+
timer.unref?.();
|
|
148
|
+
});
|
|
149
|
+
let message;
|
|
150
|
+
try {
|
|
151
|
+
// The race, not the signal alone, bounds the wait: a provider
|
|
152
|
+
// that ignores the abort must not wedge the chain (the zombie
|
|
153
|
+
// promise is abandoned; Promise.race keeps its rejection handled).
|
|
154
|
+
message = await Promise.race([
|
|
155
|
+
deps.models.completeSimple(model, {
|
|
156
|
+
systemPrompt: NAMING_SYSTEM_PROMPT,
|
|
157
|
+
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
|
158
|
+
}, { signal: controller.signal }),
|
|
159
|
+
timedOut,
|
|
160
|
+
]);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
if (timer !== undefined)
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
}
|
|
166
|
+
// An errored or aborted completion is a failure, not a title (the
|
|
167
|
+
// message may still carry text content alongside the error).
|
|
168
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
return cleanTitle(textOf(message.content));
|
|
172
|
+
}
|
|
173
|
+
/** Commit one attempt bump (the fresh count, read inside the commit). */
|
|
174
|
+
async function recordFailure(id, reason) {
|
|
175
|
+
const conversation = await deps.harness.conversation(id, BACKGROUND_CONTEXT);
|
|
176
|
+
if (conversation === undefined)
|
|
177
|
+
return;
|
|
178
|
+
let attempts;
|
|
179
|
+
await conversation.commit(async (tx) => {
|
|
180
|
+
const draft = await tx.doc(SessionMetaDoc, id);
|
|
181
|
+
attempts = (draft.autoNameAttempts ?? 0) + 1;
|
|
182
|
+
draft.autoNameAttempts = attempts;
|
|
183
|
+
}, BACKGROUND_CONTEXT);
|
|
184
|
+
log(`auto-name: conversation ${String(id)} attempt ${String(attempts ?? "?")} of ${NAMING_MAX_ATTEMPTS} failed — ${reason}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** The text blocks of an assistant message, concatenated. */
|
|
188
|
+
function textOf(content) {
|
|
189
|
+
return content
|
|
190
|
+
.filter((block) => block.type === "text")
|
|
191
|
+
.map((block) => block.text)
|
|
192
|
+
.join("");
|
|
193
|
+
}
|