@sigrun/sigrun 0.1.5 → 0.2.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 +136 -0
- package/README.md +352 -36
- package/THIRD_PARTY_NOTICES.md +23 -0
- package/dist/blocks/schema.js +9 -2
- package/dist/blocks/validate.js +57 -8
- package/dist/cli.js +26 -2
- package/dist/config.js +67 -1
- package/dist/extension.d.ts +189 -15
- package/dist/harness/agent-env.js +62 -0
- package/dist/harness/ambient.js +39 -185
- package/dist/harness/blocks/entries.js +47 -0
- package/dist/harness/blocks/state.js +58 -0
- package/dist/harness/blocks/tools.js +274 -0
- package/dist/harness/boot.js +253 -20
- package/dist/harness/builtins.js +15 -11
- package/dist/harness/event-bus.js +105 -10
- package/dist/harness/identity/accounts.js +186 -0
- package/dist/harness/identity/groups.js +14 -0
- package/dist/harness/identity/index.js +13 -6
- package/dist/harness/identity/json-store.js +15 -4
- package/dist/harness/identity/setup-code-check.js +9 -0
- package/dist/harness/identity/tokens.js +28 -5
- package/dist/harness/identity/users.js +111 -12
- package/dist/harness/interactions.js +292 -136
- package/dist/harness/loader.js +271 -17
- package/dist/harness/packages.js +1 -1
- package/dist/harness/reserved-sections.js +21 -0
- package/dist/harness/seam-host.js +80 -6
- package/dist/harness/settings.js +63 -2
- package/dist/harness/stores.js +241 -4
- package/dist/harness/user-services.js +391 -0
- package/dist/harness/user-tools.js +187 -0
- package/dist/harness/workspace.js +61 -55
- package/dist/jobs/fire.js +10 -16
- package/dist/sandbox/availability.js +162 -0
- package/dist/sandbox/command-observer.js +59 -0
- package/dist/sandbox/grants.js +159 -0
- package/dist/sandbox/helper.js +29 -0
- package/dist/sandbox/isolation.js +29 -0
- package/dist/sandbox/launch.js +20 -0
- package/dist/sandbox/os-account.js +49 -0
- package/dist/sandbox/policy.js +253 -0
- package/dist/sandbox/pool.js +548 -0
- package/dist/sandbox/protocol.js +167 -0
- package/dist/sandbox/sandboxed-env.js +245 -0
- package/dist/sandbox/unavailable-env.js +49 -0
- package/dist/sandbox/worker.js +281 -0
- package/dist/serve.js +82 -7
- package/dist/server/auth/access-principal.js +16 -0
- package/dist/server/auth/pipeline.js +47 -33
- package/dist/server/blocks/answers.js +94 -0
- package/dist/server/blocks/chat-surfaces.js +393 -0
- package/dist/server/blocks/notes.js +216 -0
- package/dist/server/blocks/summaries.js +328 -0
- package/dist/server/commands.js +5 -0
- package/dist/server/content-parts-lazy.js +13 -0
- package/dist/server/conversation-handler.js +2 -5
- package/dist/server/conversation-ops.js +138 -37
- package/dist/server/helpers.js +18 -6
- package/dist/server/host-bridge.js +40 -20
- package/dist/server/images/cache.js +205 -0
- package/dist/server/images/convert.js +13 -0
- package/dist/server/images/fetch.js +302 -0
- package/dist/server/images/ranges.js +42 -0
- package/dist/server/images/rewrite.js +94 -0
- package/dist/server/images/sign.js +84 -0
- package/dist/server/images/sniff.js +62 -0
- package/dist/server/index.js +144 -4
- package/dist/server/login/flows.js +126 -0
- package/dist/server/login/http.js +31 -0
- package/dist/server/login/resolve.js +133 -0
- package/dist/server/login/setup-claim.js +55 -0
- package/dist/server/login/usernames.js +15 -0
- package/dist/server/ownership.js +13 -0
- package/dist/server/routes/admin.js +58 -0
- package/dist/server/routes/auth.js +88 -2
- package/dist/server/routes/conversations.js +3 -6
- package/dist/server/routes/events.js +28 -3
- package/dist/server/routes/extensions.js +37 -5
- package/dist/server/routes/images.js +267 -0
- package/dist/server/routes/interactions.js +57 -68
- package/dist/server/routes/jobs.js +2 -8
- package/dist/server/routes/login.js +223 -0
- package/dist/server/routes/me.js +90 -0
- package/dist/server/routes/meta.js +15 -0
- package/dist/server/routes/onboarding.js +94 -84
- package/dist/server/routes/settings-sections.js +190 -0
- package/dist/server/routes/settings.js +16 -4
- package/dist/server/routes/submissions.js +1 -1
- package/dist/server/routes/surfaces.js +175 -23
- package/dist/server/routes/users.js +116 -20
- package/dist/server/schema-fixture.js +213 -0
- package/dist/server/session-meta.js +3 -0
- package/dist/server/shutdown.js +11 -4
- package/dist/server/static.js +18 -0
- package/dist/server/surfaces/dedup.js +9 -2
- package/dist/server/surfaces/service.js +181 -13
- package/dist/server/surfaces/settings-surfaces.js +597 -0
- package/dist/server/turns/submit-turn.js +26 -0
- package/dist/server/views.js +16 -0
- package/dist/settings/core-sections.js +532 -0
- package/dist/settings/pending.js +181 -0
- package/dist/settings/registry.js +453 -0
- package/dist/skills/index.js +6 -0
- package/node_modules/@sigrun/shared/CHANGELOG.md +15 -0
- package/node_modules/@sigrun/shared/dist/access.d.ts +4 -2
- package/node_modules/@sigrun/shared/dist/access.js +11 -4
- package/node_modules/@sigrun/shared/dist/ambient.d.ts +17 -74
- package/node_modules/@sigrun/shared/dist/ambient.js +8 -6
- package/node_modules/@sigrun/shared/dist/blocks/text.js +16 -2
- package/node_modules/@sigrun/shared/dist/blocks/transport.d.ts +4 -0
- package/node_modules/@sigrun/shared/dist/blocks/types.d.ts +21 -4
- package/node_modules/@sigrun/shared/dist/interactions.d.ts +22 -48
- package/node_modules/@sigrun/shared/dist/interactions.js +9 -7
- package/node_modules/@sigrun/shared/dist/settings.d.ts +27 -2
- package/node_modules/@sigrun/shared/dist/themes.d.ts +2 -3
- package/node_modules/@sigrun/shared/dist/themes.js +2 -3
- package/node_modules/@sigrun/shared/dist/turns.d.ts +14 -0
- package/node_modules/@sigrun/shared/dist/turns.js +53 -3
- package/node_modules/@sigrun/shared/dist/views.d.ts +175 -12
- package/node_modules/@sigrun/shared/dist/views.js +16 -0
- package/node_modules/@sigrun/shared/package.json +1 -1
- package/package.json +13 -8
- package/webui/dist/assets/index-CxB969qp.js +55 -0
- package/webui/dist/assets/index-yqkL6BSS.css +1 -0
- package/webui/dist/index.html +2 -2
- package/webui/dist/assets/index-B4-GYqEb.js +0 -50
- package/webui/dist/assets/index-BiSMljer.css +0 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chat-blocks core tools (UI-blocks U5 Task 1, spec §8.1): the
|
|
3
|
+
* `sigrun.blocks` pi-durable extension installed by `openHarness` beside
|
|
4
|
+
* the coding/search tools, so EVERY agent has them.
|
|
5
|
+
*
|
|
6
|
+
* - `show_blocks {title, tree, data}` validates the tree against the
|
|
7
|
+
* `chat` placement (§5.7: no `page` root, no `secret` fields; §4.5
|
|
8
|
+
* limits 200 blocks / 256 KiB tree / 256 KiB data; handler names are
|
|
9
|
+
* free labels in chat — no `handlers` set). Validation errors go back
|
|
10
|
+
* to the model as the tool error, each naming its JSON path (§14.2).
|
|
11
|
+
* On success it appends ONE immutable `sigrun.blocks` entry and seeds
|
|
12
|
+
* the state family `{rev: 0, data, changes: [], notedThrough: 0}` in
|
|
13
|
+
* the SAME commit; the tool result is the §14.3 text projection of the
|
|
14
|
+
* tree and its initial data plus the `entryId`.
|
|
15
|
+
* - `update_blocks {entryId, ops}` applies `DataOp`s to the surface's
|
|
16
|
+
* CURRENT data (spec §8.1's stale-index honesty: an op written against
|
|
17
|
+
* old indexes applies to current data — the returned projection shows
|
|
18
|
+
* the ACTUAL resulting order). It records NO change record (the agent
|
|
19
|
+
* made the change), increments `rev`, publishes
|
|
20
|
+
* `surface.changed {conversationId, entryId, rev}` through the publish
|
|
21
|
+
* seam, and its result is the NEW projection. A `DataOpError` (a
|
|
22
|
+
* missing path, a non-array where one is required) returns a tool
|
|
23
|
+
* error naming the op index; a failed batch leaves no partial write
|
|
24
|
+
* (the throw unwinds the commit before anything persisted). An entry id
|
|
25
|
+
* from another conversation (or one this conversation cannot see) is
|
|
26
|
+
* "no such block in this conversation".
|
|
27
|
+
*
|
|
28
|
+
* The publish seam (`subscribeChatSurfaceChanged`): the chat
|
|
29
|
+
* `surface.changed` wire shape joins `HostEventWire` in Task 2 (shared/
|
|
30
|
+
* views.ts), and the server binds the hub publish then. Until that
|
|
31
|
+
* bind, the seam carries the events to in-process listeners only — the
|
|
32
|
+
* default subscriber set is empty, so nothing is lost by not wiring it.
|
|
33
|
+
*/
|
|
34
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
35
|
+
import { defineExtension, defineTool, } from "@earendil-works/pi-durable";
|
|
36
|
+
import { applyOps, DataOpError, toText } from "@sigrun/shared/blocks";
|
|
37
|
+
import { validateData, validateTree } from "../../blocks/validate.js";
|
|
38
|
+
import { BlocksEntry } from "./entries.js";
|
|
39
|
+
import { BlocksStateDoc } from "./state.js";
|
|
40
|
+
/** The §14.3 projection width for what the model sees of its own blocks (spec §8.1). */
|
|
41
|
+
export const PROJECTION_WIDTH = 100;
|
|
42
|
+
/**
|
|
43
|
+
* The server locale for model-facing projections (spec §14.3: "the model
|
|
44
|
+
* projection always uses the server's locale"). No locale setting exists
|
|
45
|
+
* in config today; `en-CA` is the repo's projection convention (the
|
|
46
|
+
* jobs run-namer and the block-text snapshots both use it).
|
|
47
|
+
*/
|
|
48
|
+
export const PROJECTION_LOCALE = "en-CA";
|
|
49
|
+
/**
|
|
50
|
+
* The tool-description sentence (spec §8.1, verbatim): it teaches the
|
|
51
|
+
* action kinds — silent saves, immediate submits, `turn: true` only when
|
|
52
|
+
* a reply is needed.
|
|
53
|
+
*/
|
|
54
|
+
const ACTION_SENTENCE = "Interactive elements save the user's edits silently; you'll receive a summary of edits at your next turn. A `submit` sends you the result immediately; set `turn: true` on a `move` or `edit` only if each one needs an immediate reply.";
|
|
55
|
+
/** The publish seam's listener set (see `chatSurfaceChanged`). */
|
|
56
|
+
const chatSurfaceChangedListeners = new Set();
|
|
57
|
+
/**
|
|
58
|
+
* Publish one chat-surface change through the seam (spec §8.2's
|
|
59
|
+
* `surface.changed {conversation, entry, rev}`). The TOOL layer calls
|
|
60
|
+
* this after its state commit; the server (Task 2) subscribes and fans
|
|
61
|
+
* the event out to the watcher registry + hub. In-process subscribers
|
|
62
|
+
* see every event; a listener that throws is logged, never propagated
|
|
63
|
+
* into the tool.
|
|
64
|
+
*/
|
|
65
|
+
export function chatSurfaceChanged(event) {
|
|
66
|
+
for (const listener of chatSurfaceChangedListeners) {
|
|
67
|
+
try {
|
|
68
|
+
listener(event);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error("blocks: surface.changed listener failed:", error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Subscribe to chat-surface changes (the test seam and Task 2's server
|
|
77
|
+
* binding point). Returns the unsubscribe.
|
|
78
|
+
*/
|
|
79
|
+
export function subscribeChatSurfaceChanged(listener) {
|
|
80
|
+
chatSurfaceChangedListeners.add(listener);
|
|
81
|
+
return () => {
|
|
82
|
+
chatSurfaceChangedListeners.delete(listener);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** A tool error: text content the model can read and recover from. */
|
|
86
|
+
function toolError(message) {
|
|
87
|
+
return { output: [{ type: "text", text: message }], isError: true };
|
|
88
|
+
}
|
|
89
|
+
/** A plain-object check (the `data` arg arrives as `unknown`). */
|
|
90
|
+
function isJsonObject(value) {
|
|
91
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
92
|
+
}
|
|
93
|
+
/** The show_blocks tool (spec §8.1). */
|
|
94
|
+
export const showBlocksTool = defineTool({
|
|
95
|
+
name: "show_blocks",
|
|
96
|
+
description: "Show the user an interactive UI (tables, charts, forms, lists) in the conversation. " +
|
|
97
|
+
"The result is the plain-text projection of the surface plus its entryId; keep that entryId to update the surface later. " +
|
|
98
|
+
ACTION_SENTENCE,
|
|
99
|
+
parameters: Type.Object({
|
|
100
|
+
title: Type.String({
|
|
101
|
+
description: "The surface's title, shown in the channel's header.",
|
|
102
|
+
}),
|
|
103
|
+
tree: Type.Unknown({
|
|
104
|
+
description: "The block tree {v: 1, root: Block} — the chat subset (no page root, no secret fields; at most 200 blocks).",
|
|
105
|
+
}),
|
|
106
|
+
data: Type.Unknown({
|
|
107
|
+
description: "The initial data object the tree's bindings point into (a JSON object).",
|
|
108
|
+
}),
|
|
109
|
+
}),
|
|
110
|
+
async execute(args, api, context) {
|
|
111
|
+
// Validate first (§8.1): an invalid tree appends NOTHING. Handler
|
|
112
|
+
// names are free labels in chat — no `handlers` set.
|
|
113
|
+
const validation = validateTree(args.tree, { placement: "chat" });
|
|
114
|
+
if (!validation.ok) {
|
|
115
|
+
const lines = validation.errors.map((error) => `${error.path}: ${error.message}`);
|
|
116
|
+
return toolError(`show_blocks: the tree is invalid:\n${lines.join("\n")}`);
|
|
117
|
+
}
|
|
118
|
+
if (!isJsonObject(args.data)) {
|
|
119
|
+
return toolError("show_blocks: data must be a JSON object");
|
|
120
|
+
}
|
|
121
|
+
const data = args.data;
|
|
122
|
+
// The data limit (§4.5, chat row) — validateTree covers the tree;
|
|
123
|
+
// the data object gets its own check.
|
|
124
|
+
const dataCheck = validateData(data, "chat");
|
|
125
|
+
if (!dataCheck.ok) {
|
|
126
|
+
return toolError(`show_blocks: ${dataCheck.errors
|
|
127
|
+
.map((error) => `${error.path}: ${error.message}`)
|
|
128
|
+
.join("\n")}`);
|
|
129
|
+
}
|
|
130
|
+
const tree = validation.tree;
|
|
131
|
+
// One commit: append the immutable entry and seed the state family
|
|
132
|
+
// (spec §8.2) — a crash between the two cannot strand a block
|
|
133
|
+
// without state.
|
|
134
|
+
const entryId = await api.commit(async (tx) => {
|
|
135
|
+
const entry = await tx.appendEntry(BlocksEntry, api.conversationId, {
|
|
136
|
+
data: {
|
|
137
|
+
title: args.title,
|
|
138
|
+
tree: tree,
|
|
139
|
+
data,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
const state = await tx.doc(BlocksStateDoc, api.conversationId, String(entry.id), String(entry.id));
|
|
143
|
+
state.rev = 0;
|
|
144
|
+
state.data = data;
|
|
145
|
+
state.changes = [];
|
|
146
|
+
state.notedThrough = 0;
|
|
147
|
+
return entry.id;
|
|
148
|
+
}, context);
|
|
149
|
+
return {
|
|
150
|
+
output: [
|
|
151
|
+
{
|
|
152
|
+
type: "text",
|
|
153
|
+
text: `${toText(tree, data, {
|
|
154
|
+
width: PROJECTION_WIDTH,
|
|
155
|
+
locale: PROJECTION_LOCALE,
|
|
156
|
+
})}\nentryId: ${String(entryId)}`,
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
/** The update_blocks tool (spec §8.1). */
|
|
163
|
+
export const updateBlocksTool = defineTool({
|
|
164
|
+
name: "update_blocks",
|
|
165
|
+
description: "Apply data ops (set/remove/insert/move) to one of this conversation's block surfaces (by its entryId) and see the surface's CURRENT text projection. " +
|
|
166
|
+
"Ops apply to current data — if the user has reordered things since you last looked, index-based ops land on the new indexes. " +
|
|
167
|
+
ACTION_SENTENCE,
|
|
168
|
+
parameters: Type.Object({
|
|
169
|
+
entryId: Type.String({
|
|
170
|
+
description: "The entryId show_blocks returned.",
|
|
171
|
+
}),
|
|
172
|
+
ops: Type.Array(Type.Unknown(), {
|
|
173
|
+
description: "The DataOps to apply, in order.",
|
|
174
|
+
}),
|
|
175
|
+
}),
|
|
176
|
+
async execute(args, api, context) {
|
|
177
|
+
const parsed = parseEntryId(args.entryId);
|
|
178
|
+
if (parsed === undefined) {
|
|
179
|
+
return toolError(`update_blocks: entryId ${JSON.stringify(args.entryId)} is not one this conversation has`);
|
|
180
|
+
}
|
|
181
|
+
// Structural op validation (the schema is Unknown per element): each
|
|
182
|
+
// element must carry a known op tag; anything else is a recoverable
|
|
183
|
+
// model error naming the element's index.
|
|
184
|
+
const elements = args.ops;
|
|
185
|
+
// SAFETY: the schema admits unknown elements (typebox cannot express
|
|
186
|
+
// the DataOp union without duplicating it); the loop below validates
|
|
187
|
+
// each element's op tag, and applyOps re-validates every path — an
|
|
188
|
+
// element that is not a DataOp shape is a tool error, never a
|
|
189
|
+
// misapplied write.
|
|
190
|
+
const ops = elements;
|
|
191
|
+
for (let index = 0; index < elements.length; index++) {
|
|
192
|
+
const tag = elements[index]?.op;
|
|
193
|
+
if (tag !== "set" &&
|
|
194
|
+
tag !== "remove" &&
|
|
195
|
+
tag !== "insert" &&
|
|
196
|
+
tag !== "move") {
|
|
197
|
+
return toolError(`update_blocks: op ${index} is not a DataOp (expected {op: "set"|"remove"|"insert"|"move", …})`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// The write: one commit reads the CURRENT state, applies the ops,
|
|
201
|
+
// bumps rev. The tree comes from the entry record — returned only
|
|
202
|
+
// when visible through THIS conversation (fork-aware), which is the
|
|
203
|
+
// same check that makes a foreign id "no such block".
|
|
204
|
+
let updated;
|
|
205
|
+
try {
|
|
206
|
+
updated = await api.commit(async (tx) => {
|
|
207
|
+
const page = await tx.scanEntries({
|
|
208
|
+
conversationId: api.conversationId,
|
|
209
|
+
minEntryId: parsed,
|
|
210
|
+
maxEntryId: parsed,
|
|
211
|
+
}, 1, undefined);
|
|
212
|
+
const record = page.items[0];
|
|
213
|
+
if (record === undefined || !BlocksEntry.is(record)) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
const state = await tx.doc(BlocksStateDoc, api.conversationId, String(parsed), String(parsed));
|
|
217
|
+
// applyOps throws DataOpError (naming the op index) on a bad
|
|
218
|
+
// path; the throw unwinds the commit BEFORE anything persists
|
|
219
|
+
// — a failed batch leaves no partial write.
|
|
220
|
+
state.data = applyOps(state.data, ops);
|
|
221
|
+
state.rev += 1;
|
|
222
|
+
// Snapshot to plain JSON: chord wraps draft values in tracking
|
|
223
|
+
// proxies that settle when the commit ends — returning the draft
|
|
224
|
+
// value itself would throw "Cannot use a settled overlay" when
|
|
225
|
+
// the projection reads it after commit.
|
|
226
|
+
return {
|
|
227
|
+
tree: record.data.tree,
|
|
228
|
+
data: JSON.parse(JSON.stringify(state.data)),
|
|
229
|
+
rev: state.rev,
|
|
230
|
+
};
|
|
231
|
+
}, context);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
if (error instanceof DataOpError) {
|
|
235
|
+
return toolError(`update_blocks: op ${error.opIndex} failed: ${error.message}`);
|
|
236
|
+
}
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
if (updated === undefined) {
|
|
240
|
+
return toolError(`update_blocks: no such block in this conversation (entryId ${JSON.stringify(args.entryId)})`);
|
|
241
|
+
}
|
|
242
|
+
// The change announcement (§8.2): surface.changed to watchers of
|
|
243
|
+
// this conversation's block.
|
|
244
|
+
chatSurfaceChanged({
|
|
245
|
+
type: "surface.changed",
|
|
246
|
+
conversationId: api.conversationId,
|
|
247
|
+
entryId: args.entryId,
|
|
248
|
+
rev: updated.rev,
|
|
249
|
+
});
|
|
250
|
+
return {
|
|
251
|
+
output: [
|
|
252
|
+
{
|
|
253
|
+
type: "text",
|
|
254
|
+
text: toText(updated.tree, updated.data, {
|
|
255
|
+
width: PROJECTION_WIDTH,
|
|
256
|
+
locale: PROJECTION_LOCALE,
|
|
257
|
+
}),
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
/** A numeric entry id (`EntryId` is a branded number; tool args are strings). */
|
|
264
|
+
export function parseEntryId(value) {
|
|
265
|
+
if (!/^\d+$/.test(value))
|
|
266
|
+
return undefined;
|
|
267
|
+
const id = Number(value);
|
|
268
|
+
return Number.isSafeInteger(id) && id > 0 ? id : undefined;
|
|
269
|
+
}
|
|
270
|
+
/** The core `sigrun.blocks` extension: the two tools, installed for every conversation. */
|
|
271
|
+
export const blocksToolsExtension = defineExtension({
|
|
272
|
+
name: "sigrun.blocks",
|
|
273
|
+
tools: [showBlocksTool, updateBlocksTool],
|
|
274
|
+
});
|