@sigrun/sigrun 0.4.2 → 0.5.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 +37 -1
- package/README.md +46 -23
- package/dist/commands/expand.js +37 -5
- package/dist/curation/attribution.js +135 -0
- package/dist/curation/deletion.js +32 -0
- package/dist/curation/gate.js +50 -0
- package/dist/curation/host.js +342 -0
- package/dist/curation/internal.js +54 -0
- package/dist/curation/names.js +5 -0
- package/dist/curation/overlays.js +325 -0
- package/dist/curation/paths.js +17 -0
- package/dist/curation/run-doc.js +64 -0
- package/dist/curation/runs.js +579 -0
- package/dist/curation/scan.js +30 -0
- package/dist/curation/skill-tool.js +308 -0
- package/dist/curation/store.js +444 -0
- package/dist/curation/targets.js +77 -0
- package/dist/curation/usage.js +124 -0
- package/dist/curation/wrap.js +212 -0
- package/dist/extension.d.ts +282 -0
- package/dist/harness/boot.js +209 -9
- package/dist/harness/builtins.js +12 -9
- package/dist/harness/event-bus.js +64 -0
- package/dist/harness/loader.js +114 -1
- package/dist/harness/naming.js +6 -0
- package/dist/harness/packages.js +14 -9
- package/dist/harness/reserved-sections.js +2 -0
- package/dist/harness/seam-host.js +139 -26
- package/dist/harness/settings.js +2 -2
- package/dist/harness/tools.js +70 -0
- package/dist/jobs/fire.js +33 -11
- package/dist/jobs/skills-refs.js +40 -0
- package/dist/sandbox/policy.js +14 -0
- package/dist/server/blocks/answers.js +9 -3
- package/dist/server/conversation-ops.js +21 -3
- package/dist/server/helpers.js +1 -1
- package/dist/server/host-bridge.js +6 -1
- package/dist/server/notifications.js +6 -0
- package/dist/server/routes/meta.js +8 -3
- package/dist/server/routes/submissions.js +5 -0
- package/dist/server/routes/surfaces.js +3 -1
- package/dist/server/routes/users.js +7 -0
- package/dist/server/schema-fixture.js +168 -0
- package/dist/server/session-meta.js +12 -10
- package/dist/settings/core-sections.js +9 -0
- package/dist/settings/registry.js +6 -0
- package/dist/skills/index.js +88 -19
- package/node_modules/@sigrun/shared/CHANGELOG.md +3 -1
- package/node_modules/@sigrun/shared/dist/access.d.ts +3 -0
- package/node_modules/@sigrun/shared/dist/access.js +8 -0
- package/node_modules/@sigrun/shared/dist/session-meta.d.ts +15 -4
- package/node_modules/@sigrun/shared/dist/settings.d.ts +7 -5
- package/node_modules/@sigrun/shared/package.json +1 -1
- package/package.json +9 -7
- package/webui/dist/assets/{index-DWmsAQl2.js → index-BtczbUXo.js} +1 -1
- package/webui/dist/index.html +1 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hints by wrapping (curator P9, spec §6.6): the interception that turns
|
|
3
|
+
* a curation-tool call outside a curator run into a review hint instead
|
|
4
|
+
* of a write.
|
|
5
|
+
*
|
|
6
|
+
* WHY WRAPPING, NOT A HOOK: a `beforeTool` hook gets a read-only
|
|
7
|
+
* `HookApi` and cannot commit a hint, and a hook only runs where its
|
|
8
|
+
* extension is selected. Core instead rewrites the registration of every
|
|
9
|
+
* curation tool at load, so the interception travels with the tool into
|
|
10
|
+
* every conversation, whatever its selection, and runs inside
|
|
11
|
+
* `execute` where `api.commit` exists.
|
|
12
|
+
*
|
|
13
|
+
* The wrap oracle is the FEED (`createCurationToolFeed`): a mutable set
|
|
14
|
+
* of curation tool names that starts with the core set (`skill_manage`)
|
|
15
|
+
* and grows as the loader validates each extension's `curationTargets`.
|
|
16
|
+
* Boot wraps the skills extension's tools at its install (the feed's
|
|
17
|
+
* core entries cover `skill_manage`); the loader wraps any of an
|
|
18
|
+
* extension's tools whose names are in the feed right before
|
|
19
|
+
* `registry.install`. The read-side API stays
|
|
20
|
+
* `CurationRegistry.isCurationTool` (A7) — the feed is only the
|
|
21
|
+
* write-side wrap oracle, not the registry.
|
|
22
|
+
*
|
|
23
|
+
* Wrap semantics (`wrapCurationTool`):
|
|
24
|
+
*
|
|
25
|
+
* 1. inside a curator run (`sigrun.curation.run` present) → delegate to
|
|
26
|
+
* the original, untouched (the gate — `sigrun.curation-gate` — owns
|
|
27
|
+
* the allow enforcement there);
|
|
28
|
+
* 2. otherwise a MUTATING call never executes. `skill_manage`'s read
|
|
29
|
+
* actions (`list`/`view`) pass through — they are safe anywhere. The
|
|
30
|
+
* current run's input submissions (LiveDoc's `run.inputs`) are each
|
|
31
|
+
* resolved with `authorOfSubmission` and matched against the
|
|
32
|
+
* conversation owner: owner present AND every input authored by the
|
|
33
|
+
* owner → append to the `sigrun.curation.hints` doc and answer
|
|
34
|
+
* "Noted. The curator will review this after your reply."; ANYTHING
|
|
35
|
+
* else (a non-owner or unattributed input, an ownerless or runless
|
|
36
|
+
* conversation) → "Not recorded: this is only learned from the
|
|
37
|
+
* owner's own messages." Neither case executes the original.
|
|
38
|
+
*/
|
|
39
|
+
import { randomUUID } from "node:crypto";
|
|
40
|
+
import { copyJson } from "@earendil-works/chord";
|
|
41
|
+
import { defineDoc, LiveDoc } from "@earendil-works/pi-durable";
|
|
42
|
+
import { authorOfSubmission } from "./attribution.js";
|
|
43
|
+
import { curationRun } from "./run-doc.js";
|
|
44
|
+
import { CORE_CURATION_TOOLS } from "./targets.js";
|
|
45
|
+
import { SessionMetaDoc } from "../server/session-meta.js";
|
|
46
|
+
/** The answer an owner-authored call receives (contract text). */
|
|
47
|
+
export const HINT_NOTED_TEXT = "Noted. The curator will review this after your reply.";
|
|
48
|
+
/** The answer every other call receives (contract text). */
|
|
49
|
+
export const HINT_NOT_RECORDED_TEXT = "Not recorded: this is only learned from the owner's own messages.";
|
|
50
|
+
/**
|
|
51
|
+
* The `sigrun.curation.hints` doc (latest-only singleton per
|
|
52
|
+
* conversation): the pending hints the next review of THIS conversation
|
|
53
|
+
* consumes. `fork: "current"` (the session-meta/authors pattern) — a
|
|
54
|
+
* fork review (P6) inherits the parent's pending hints, exactly the
|
|
55
|
+
* transcript it reviews.
|
|
56
|
+
*/
|
|
57
|
+
export const HintsDoc = defineDoc({
|
|
58
|
+
kind: "sigrun.curation.hints",
|
|
59
|
+
version: 1,
|
|
60
|
+
scope: "conversation",
|
|
61
|
+
checkpointWhen: () => false,
|
|
62
|
+
history: "latest",
|
|
63
|
+
fork: "current",
|
|
64
|
+
initial: () => ({ hints: [] }),
|
|
65
|
+
});
|
|
66
|
+
/** `skill_manage`'s read actions — safe anywhere, never intercepted. */
|
|
67
|
+
const SKILL_MANAGE_READ_ACTIONS = new Set([
|
|
68
|
+
"list",
|
|
69
|
+
"view",
|
|
70
|
+
]);
|
|
71
|
+
/**
|
|
72
|
+
* Whether one call passes through to the original outside a curator
|
|
73
|
+
* run: only `skill_manage`'s read actions. Every other curation-tool
|
|
74
|
+
* call is a write intent — the target's listed tools ARE its write
|
|
75
|
+
* surface, so the action set needs no per-target spelling.
|
|
76
|
+
*/
|
|
77
|
+
function passesThrough(tool, args) {
|
|
78
|
+
if (tool !== "skill_manage")
|
|
79
|
+
return false;
|
|
80
|
+
const action = args?.action;
|
|
81
|
+
return typeof action === "string" && SKILL_MANAGE_READ_ACTIONS.has(action);
|
|
82
|
+
}
|
|
83
|
+
/** The conversation owner through any document reader (fail-closed). */
|
|
84
|
+
async function ownerOf(api, ctx) {
|
|
85
|
+
try {
|
|
86
|
+
return (await api.snapshot(SessionMetaDoc, api.conversationId, ctx))?.owner;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** The current run's input submission ids (fail-closed to none). */
|
|
93
|
+
async function liveInputsOf(api, ctx) {
|
|
94
|
+
try {
|
|
95
|
+
return (await api.snapshot(LiveDoc, api.conversationId, ctx))?.run?.inputs ?? [];
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether `owner` authored EVERY one of the inputs. An input with no
|
|
103
|
+
* author record (the pre-write window, or a caller that records none)
|
|
104
|
+
* resolves `undefined` and fails the check — never a guess. A read
|
|
105
|
+
* failure also fails (fail closed).
|
|
106
|
+
*/
|
|
107
|
+
async function everyInputOwnerAuthored(api, inputs, owner, ctx) {
|
|
108
|
+
for (const input of inputs) {
|
|
109
|
+
let author;
|
|
110
|
+
try {
|
|
111
|
+
author = await authorOfSubmission(api, api.conversationId, input, ctx);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
author = undefined;
|
|
115
|
+
}
|
|
116
|
+
if (author !== owner)
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
/** One text tool result. */
|
|
122
|
+
function text(text) {
|
|
123
|
+
return { output: [{ type: "text", text }] };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Wrap one curation tool's registration: byte-identical name, schema,
|
|
127
|
+
* description and every other field (the spread keeps each reference) —
|
|
128
|
+
* only `execute` is rewritten, per the wrap semantics in the module
|
|
129
|
+
* doc. Generic so a typed registration comes back typed.
|
|
130
|
+
*/
|
|
131
|
+
export function wrapCurationTool(tool) {
|
|
132
|
+
const original = tool.execute;
|
|
133
|
+
const wrapped = {
|
|
134
|
+
...tool,
|
|
135
|
+
async execute(args, api, context) {
|
|
136
|
+
// 1. Inside a curator run the original runs untouched (the gate
|
|
137
|
+
// owns the allow enforcement; the store user is the run's).
|
|
138
|
+
const run = await curationRun(api, api.conversationId, context);
|
|
139
|
+
if (run !== undefined) {
|
|
140
|
+
return original.call(tool, args, api, context);
|
|
141
|
+
}
|
|
142
|
+
// 2. Outside a run: the read actions of `skill_manage` pass
|
|
143
|
+
// through; every other call is a write intent that never
|
|
144
|
+
// executes.
|
|
145
|
+
if (passesThrough(tool.name, args)) {
|
|
146
|
+
return original.call(tool, args, api, context);
|
|
147
|
+
}
|
|
148
|
+
const owner = await ownerOf(api, context);
|
|
149
|
+
// No live run means no owner-authored evidence — the hint needs
|
|
150
|
+
// inputs, and every one of them authored by the owner.
|
|
151
|
+
const inputs = await liveInputsOf(api, context);
|
|
152
|
+
const ownerAuthored = owner !== undefined &&
|
|
153
|
+
inputs.length > 0 &&
|
|
154
|
+
(await everyInputOwnerAuthored(api, inputs, owner, context));
|
|
155
|
+
if (ownerAuthored) {
|
|
156
|
+
const hint = {
|
|
157
|
+
id: randomUUID(),
|
|
158
|
+
tool: tool.name,
|
|
159
|
+
args: args,
|
|
160
|
+
at: new Date().toISOString(),
|
|
161
|
+
};
|
|
162
|
+
await api.commit(async (tx) => {
|
|
163
|
+
const doc = await tx.doc(HintsDoc, api.conversationId);
|
|
164
|
+
doc.hints = [...doc.hints, hint];
|
|
165
|
+
}, context);
|
|
166
|
+
return text(HINT_NOTED_TEXT);
|
|
167
|
+
}
|
|
168
|
+
return text(HINT_NOT_RECORDED_TEXT);
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
return wrapped;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The wrap feed: the mutable set of curation tool names — the core set
|
|
175
|
+
* (`skill_manage`) plus every listed target tool the loader validates.
|
|
176
|
+
* Boot creates the ONE feed and threads it into the loader; the built
|
|
177
|
+
* `CurationRegistry` (A7) is the read-side view, never this set.
|
|
178
|
+
*/
|
|
179
|
+
export function createCurationToolFeed() {
|
|
180
|
+
return new Set(CORE_CURATION_TOOLS);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Wrap every curation-tool registration of one extension in place of
|
|
184
|
+
* its install. Identity when the feed is absent or names none of the
|
|
185
|
+
* extension's tools — the untouched object, not a copy, so nothing
|
|
186
|
+
* downstream observes a swap.
|
|
187
|
+
*/
|
|
188
|
+
export function wrapExtensionCurationTools(extension, feed) {
|
|
189
|
+
if (feed === undefined)
|
|
190
|
+
return extension;
|
|
191
|
+
if (!extension.tools?.some((tool) => feed.has(tool.name)))
|
|
192
|
+
return extension;
|
|
193
|
+
return {
|
|
194
|
+
...extension,
|
|
195
|
+
tools: extension.tools.map((tool) => feed.has(tool.name) ? wrapCurationTool(tool) : tool),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Read and clear a conversation's hints in ONE commit (the host-side
|
|
200
|
+
* drain): the hints come out exactly once — the review that takes them
|
|
201
|
+
* owns them, and a second take reads empty.
|
|
202
|
+
*/
|
|
203
|
+
export async function takeHints(conversation, ctx) {
|
|
204
|
+
return conversation.commit(async (tx) => {
|
|
205
|
+
const doc = await tx.doc(HintsDoc, conversation.id);
|
|
206
|
+
// copyJson INSIDE the callback: the draft's values are tracker
|
|
207
|
+
// overlays that die with the commit — the caller reads plain JSON.
|
|
208
|
+
const taken = copyJson(doc.hints);
|
|
209
|
+
doc.hints = [];
|
|
210
|
+
return taken;
|
|
211
|
+
}, ctx);
|
|
212
|
+
}
|
package/dist/extension.d.ts
CHANGED
|
@@ -211,6 +211,273 @@ export interface ExtensionWorker {
|
|
|
211
211
|
/** The work; rejects (or overruns the worker deadline) to fail the run. */
|
|
212
212
|
run(context: HandlerContext, wake?: WorkerWake): Promise<void>;
|
|
213
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* The outcome of one curator-run driven call (a fork review or a digest
|
|
216
|
+
* run): the final text (so a target can ask for operations as text and
|
|
217
|
+
* apply them itself), the changes its tools reported, and how it ended.
|
|
218
|
+
*/
|
|
219
|
+
export type CurationRunResult = {
|
|
220
|
+
/** How the run ended: answered, human-cancelled, failed, or deadlined. */
|
|
221
|
+
readonly status: "completed" | "cancelled" | "failed" | "deadline";
|
|
222
|
+
/** The run's final assistant text ("" when it produced none). */
|
|
223
|
+
readonly text: string;
|
|
224
|
+
/** Changes the run's tools reported, in tool-result order. */
|
|
225
|
+
readonly changes: readonly {
|
|
226
|
+
readonly changeId: string;
|
|
227
|
+
readonly summary: string;
|
|
228
|
+
/** The qualified target id the change belongs to. */
|
|
229
|
+
readonly target: string;
|
|
230
|
+
}[];
|
|
231
|
+
/** Why a non-completed run ended (the failure/cancellation reason). */
|
|
232
|
+
readonly error?: string;
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* The budget one `maintain` call runs under: a wall-clock deadline, a
|
|
236
|
+
* digest-run allowance, and the `run` entry point that starts one
|
|
237
|
+
* bounded internal digest run on the user's curator model and RETURNS
|
|
238
|
+
* its result (including the final text) — so the target can ask for
|
|
239
|
+
* operations as text and apply them itself.
|
|
240
|
+
*/
|
|
241
|
+
export interface MaintenanceBudget {
|
|
242
|
+
/** Wall-clock deadline (epoch ms); work past it is abandoned. */
|
|
243
|
+
readonly deadline: number;
|
|
244
|
+
/** Internal runs the target may start (P6 digest runs). */
|
|
245
|
+
readonly runs: number;
|
|
246
|
+
/**
|
|
247
|
+
* Start one internal digest run on the user's curator model, bounded
|
|
248
|
+
* to the listed tools; resolves with the run's result.
|
|
249
|
+
*/
|
|
250
|
+
run(input: string, tools: readonly string[]): Promise<CurationRunResult>;
|
|
251
|
+
}
|
|
252
|
+
/** One change a maintenance pass reports, undoable through `undo`. */
|
|
253
|
+
export type MaintenanceReport = {
|
|
254
|
+
/** Changes made, each undoable through the target's `undo`. */
|
|
255
|
+
readonly changes: readonly {
|
|
256
|
+
readonly changeId: string;
|
|
257
|
+
readonly summary: string;
|
|
258
|
+
}[];
|
|
259
|
+
/** True when the target stopped early and wants the next pass. */
|
|
260
|
+
readonly more?: boolean;
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* One curation target (the curator contract, spec P7): an extension's
|
|
264
|
+
* writable per-user surface the curator learns into. The `tools` list is
|
|
265
|
+
* the interception surface — every LISTED tool call outside a curator
|
|
266
|
+
* run becomes a review hint (A8's wrapping), while an unlisted direct
|
|
267
|
+
* write tool stays the extension's own. `undo` is REQUIRED: a target
|
|
268
|
+
* without it fails the load (no undo, no magic).
|
|
269
|
+
*/
|
|
270
|
+
export interface CurationTarget {
|
|
271
|
+
/** Unique within the extension; core qualifies it as `<slug>:<name>`. */
|
|
272
|
+
readonly name: string;
|
|
273
|
+
/** Tools the review may call to write this target; registered by this extension. */
|
|
274
|
+
readonly tools: readonly string[];
|
|
275
|
+
/** Routing guidance for one user; `[]` = unavailable for this user. */
|
|
276
|
+
guidance(user: string): readonly string[] | Promise<readonly string[]>;
|
|
277
|
+
/** Revert one change this target reported. Required. */
|
|
278
|
+
undo(user: string, changeId: string, ctx: HandlerContext): Promise<void>;
|
|
279
|
+
/** Optional idle maintenance, called by the curator's idle pass. */
|
|
280
|
+
maintain?(user: string, ctx: HandlerContext, budget: MaintenanceBudget): Promise<MaintenanceReport>;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* The `details.curation` shape every target tool's mutating result
|
|
284
|
+
* carries: the change id for the undo surface and a one-line summary
|
|
285
|
+
* for the UI. A type alias (not an interface): the structural JSON
|
|
286
|
+
* mirror of the tool-result details field.
|
|
287
|
+
*/
|
|
288
|
+
export type CurationChangeDetails = {
|
|
289
|
+
readonly changeId: string;
|
|
290
|
+
readonly summary: string;
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* One recorded review hint (P9): an owner-authored call to a curation
|
|
294
|
+
* tool outside a curator run, captured by A8's wrapper for the next
|
|
295
|
+
* review. `args` is the verbatim tool arguments; `at` is the capture
|
|
296
|
+
* instant (ISO).
|
|
297
|
+
*/
|
|
298
|
+
export type CurationHint = {
|
|
299
|
+
readonly id: string;
|
|
300
|
+
readonly tool: string;
|
|
301
|
+
readonly args: JsonObject;
|
|
302
|
+
readonly at: string;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* One change to a learned skill (the `skill_manage` mutating union,
|
|
306
|
+
* structural mirror of the store's `SkillChange`). A type alias (not
|
|
307
|
+
* an interface): the implicit index signature the JSON doc shapes need.
|
|
308
|
+
*/
|
|
309
|
+
export type CurationSkillChange = {
|
|
310
|
+
readonly kind: "create";
|
|
311
|
+
readonly content: string;
|
|
312
|
+
} | {
|
|
313
|
+
readonly kind: "patch";
|
|
314
|
+
readonly old: string;
|
|
315
|
+
readonly new: string;
|
|
316
|
+
readonly file?: string;
|
|
317
|
+
} | {
|
|
318
|
+
readonly kind: "write_file";
|
|
319
|
+
readonly file: string;
|
|
320
|
+
readonly content: string;
|
|
321
|
+
} | {
|
|
322
|
+
readonly kind: "remove_file";
|
|
323
|
+
readonly file: string;
|
|
324
|
+
} | {
|
|
325
|
+
readonly kind: "archive";
|
|
326
|
+
readonly absorbedInto?: string;
|
|
327
|
+
} | {
|
|
328
|
+
readonly kind: "restore";
|
|
329
|
+
readonly version?: string;
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* One change-feed entry (structural mirror of the store's `FeedEntry`).
|
|
333
|
+
* The undo-relevant fields (`before`, `after`) are opaque version ids.
|
|
334
|
+
*/
|
|
335
|
+
export type CurationFeedEntry = {
|
|
336
|
+
readonly id: string;
|
|
337
|
+
readonly at: string;
|
|
338
|
+
readonly actor: "curator" | "agent" | "user";
|
|
339
|
+
readonly action: string;
|
|
340
|
+
readonly skill: string;
|
|
341
|
+
readonly reason?: string;
|
|
342
|
+
readonly reviewId?: string;
|
|
343
|
+
readonly conversationId?: number;
|
|
344
|
+
readonly before?: string;
|
|
345
|
+
readonly after?: string;
|
|
346
|
+
};
|
|
347
|
+
/** One learned skill's listing row (structural mirror). */
|
|
348
|
+
export type CurationSkillInfo = {
|
|
349
|
+
readonly name: string;
|
|
350
|
+
readonly description: string;
|
|
351
|
+
readonly archived: boolean;
|
|
352
|
+
readonly pinned: boolean;
|
|
353
|
+
readonly bytes: number;
|
|
354
|
+
readonly createdAt: string;
|
|
355
|
+
};
|
|
356
|
+
/** Identifies one upstream skill an overlay attaches to (structural mirror). */
|
|
357
|
+
export type CurationOverlayKey = {
|
|
358
|
+
readonly root: "workspace" | "user dir" | "package";
|
|
359
|
+
readonly skill: string;
|
|
360
|
+
/** The canonical workspace root path; required for workspace keys only. */
|
|
361
|
+
readonly workspace?: string;
|
|
362
|
+
};
|
|
363
|
+
/** One stored overlay (structural mirror). */
|
|
364
|
+
export type CurationOverlay = {
|
|
365
|
+
readonly key: CurationOverlayKey;
|
|
366
|
+
readonly text: string;
|
|
367
|
+
readonly upstreamHash: string;
|
|
368
|
+
readonly orphanedSince?: string;
|
|
369
|
+
};
|
|
370
|
+
/** The actor-side metadata an overlay change records in the feed. */
|
|
371
|
+
export type CurationOverlayMeta = {
|
|
372
|
+
readonly actor: "curator" | "agent" | "user";
|
|
373
|
+
readonly reason?: string;
|
|
374
|
+
readonly reviewId?: string;
|
|
375
|
+
readonly conversationId?: number;
|
|
376
|
+
};
|
|
377
|
+
/** The bounds and identity one curator run drives under. */
|
|
378
|
+
export type CurationRunOpts = {
|
|
379
|
+
/** The instruction the run submits as its only input. */
|
|
380
|
+
readonly instruction: string;
|
|
381
|
+
/** The tools the run's gate allows (non-empty — the gate's message names them). */
|
|
382
|
+
readonly allow: readonly string[];
|
|
383
|
+
/** The tool-result budget: the run aborts at this many tool results. */
|
|
384
|
+
readonly maxToolCalls: number;
|
|
385
|
+
/** The output-token budget (advisory; see the run-driver docs). */
|
|
386
|
+
readonly maxOutputTokens: number;
|
|
387
|
+
/** The wall-clock budget in milliseconds. */
|
|
388
|
+
readonly deadlineMs: number;
|
|
389
|
+
/** The review (or digest) identity stamped on every feed entry. */
|
|
390
|
+
readonly reviewId: string;
|
|
391
|
+
/** Caller cancellation: fires → the run aborts as cancelled. */
|
|
392
|
+
readonly signal?: AbortSignal;
|
|
393
|
+
};
|
|
394
|
+
/** What a review's `eligibility` read answers. */
|
|
395
|
+
export type CurationEligibility = {
|
|
396
|
+
readonly owner?: string;
|
|
397
|
+
readonly internal: boolean;
|
|
398
|
+
/** Every user entry attributed to the owner. */
|
|
399
|
+
readonly ownerOnly: boolean;
|
|
400
|
+
/** Unattributed user entries. */
|
|
401
|
+
readonly unattributed: number;
|
|
402
|
+
readonly newestEntry?: EntryId;
|
|
403
|
+
};
|
|
404
|
+
/** One served curation target, projected for the caller. */
|
|
405
|
+
export type CurationTargetInfo = {
|
|
406
|
+
readonly id: string;
|
|
407
|
+
readonly tools: readonly string[];
|
|
408
|
+
readonly guidance: readonly string[];
|
|
409
|
+
readonly maintain: boolean;
|
|
410
|
+
};
|
|
411
|
+
/** The per-user skill-store subset the curation host serves. */
|
|
412
|
+
export type CurationSkillStore = {
|
|
413
|
+
list(): Promise<readonly CurationSkillInfo[]>;
|
|
414
|
+
view(name: string, file?: string): Promise<string>;
|
|
415
|
+
feed(limit: number, before?: string): Promise<readonly CurationFeedEntry[]>;
|
|
416
|
+
pin(name: string, pinned: boolean): Promise<void>;
|
|
417
|
+
undo(entryIds: readonly string[]): Promise<readonly CurationFeedEntry[]>;
|
|
418
|
+
/** Apply one change as the curator (actor "curator"). */
|
|
419
|
+
apply(name: string, change: CurationSkillChange, meta: {
|
|
420
|
+
readonly reviewId?: string;
|
|
421
|
+
readonly reason?: string;
|
|
422
|
+
}): Promise<CurationFeedEntry>;
|
|
423
|
+
};
|
|
424
|
+
/** The per-user overlay-store subset the curation host serves. */
|
|
425
|
+
export type CurationOverlayStore = {
|
|
426
|
+
list(): Promise<readonly CurationOverlay[]>;
|
|
427
|
+
get(key: CurationOverlayKey): Promise<CurationOverlay | undefined>;
|
|
428
|
+
put(key: CurationOverlayKey, text: string, upstreamHash: string, meta: CurationOverlayMeta): Promise<CurationFeedEntry>;
|
|
429
|
+
remove(key: CurationOverlayKey, meta: CurationOverlayMeta): Promise<CurationFeedEntry>;
|
|
430
|
+
markOrphans(present: (key: CurationOverlayKey) => boolean, now: string): Promise<void>;
|
|
431
|
+
};
|
|
432
|
+
/**
|
|
433
|
+
* The curation surface (curator P6, A9): the curator's read APIs and
|
|
434
|
+
* run producers over the curated user's per-user stores. The host
|
|
435
|
+
* owns the internals; this is the extension-facing projection — the
|
|
436
|
+
* run driver, the stores and the registry stay server-side.
|
|
437
|
+
*/
|
|
438
|
+
export type SigrunCurationHost = {
|
|
439
|
+
eligibility(id: ConversationId): Promise<CurationEligibility>;
|
|
440
|
+
/**
|
|
441
|
+
* Every user-input entry in the ACTIVE context mapped to its author
|
|
442
|
+
* (P1). An entry admitted before attribution existed, or by an
|
|
443
|
+
* unidentified caller, is present with `undefined` — unattributed,
|
|
444
|
+
* never guessed (fail closed: unattributed entries are never curation
|
|
445
|
+
* input).
|
|
446
|
+
*/
|
|
447
|
+
authors(id: ConversationId): Promise<ReadonlyMap<EntryId, string | undefined>>;
|
|
448
|
+
/** Count the conversation's assistant entries after (or all without) an anchor entry. */
|
|
449
|
+
modelCallsSince(id: ConversationId, entryId?: EntryId): Promise<number>;
|
|
450
|
+
/** Read and clear the conversation's pending review hints (A8's doc). */
|
|
451
|
+
takeHints(id: ConversationId): Promise<readonly CurationHint[]>;
|
|
452
|
+
/** Targets whose tools are all in the conversation's selection (no id: all). */
|
|
453
|
+
targets(user: string, id?: ConversationId): Promise<readonly CurationTargetInfo[]>;
|
|
454
|
+
/** The conversation's model-visible tool names (for the allow-list). */
|
|
455
|
+
tools(id: ConversationId): Promise<readonly string[]>;
|
|
456
|
+
/** Fork the conversation at its newest entry and drive the review run. */
|
|
457
|
+
forkReview(id: ConversationId, opts: CurationRunOpts): Promise<CurationRunResult>;
|
|
458
|
+
/** A fresh internal digest conversation for `user`, driven the same way. */
|
|
459
|
+
digestRun(user: string, opts: CurationRunOpts & {
|
|
460
|
+
readonly input: string;
|
|
461
|
+
readonly model?: string;
|
|
462
|
+
}): Promise<CurationRunResult>;
|
|
463
|
+
/** Run one target's maintenance pass with a digest-run budget. */
|
|
464
|
+
maintain(user: string, targetId: string, budget: Omit<MaintenanceBudget, "run">): Promise<MaintenanceReport>;
|
|
465
|
+
/** The curated user's skill store (apply = actor "curator"). */
|
|
466
|
+
skills(user: string): CurationSkillStore;
|
|
467
|
+
/** The curated user's overlay store. */
|
|
468
|
+
overlays(user: string): CurationOverlayStore;
|
|
469
|
+
/** The current upstream body + hash for one overlay key. */
|
|
470
|
+
upstream(user: string, key: CurationOverlayKey): Promise<{
|
|
471
|
+
readonly body: string;
|
|
472
|
+
readonly hash: string;
|
|
473
|
+
} | undefined>;
|
|
474
|
+
/** Skill names in the user's job prompts, paused jobs included. */
|
|
475
|
+
jobSkillRefs(user: string): Promise<ReadonlySet<string>>;
|
|
476
|
+
/** Revert every change one review made; the number of reverts. */
|
|
477
|
+
undoReview(user: string, reviewId: string): Promise<number>;
|
|
478
|
+
/** The skill → lastUsed ISO map (P4's counters). */
|
|
479
|
+
usage(user: string): Promise<Readonly<Record<string, string | undefined>>>;
|
|
480
|
+
};
|
|
214
481
|
/** The command and extra environment/read rights for one per-user process. */
|
|
215
482
|
export interface ServiceSpec {
|
|
216
483
|
readonly command: string;
|
|
@@ -1036,6 +1303,14 @@ export interface SigrunHost {
|
|
|
1036
1303
|
readonly notice?: string;
|
|
1037
1304
|
}>;
|
|
1038
1305
|
};
|
|
1306
|
+
/**
|
|
1307
|
+
* The curation surface (curator P6, A9): the curator's read APIs
|
|
1308
|
+
* (eligibility, hints, targets, tools, skills, overlays, usage, job
|
|
1309
|
+
* references) and the run producers (fork review, digest, maintain).
|
|
1310
|
+
* Binds lazily like every bridged member — a factory-body call WAITS
|
|
1311
|
+
* for the bridge, never a silent drop.
|
|
1312
|
+
*/
|
|
1313
|
+
readonly curation: SigrunCurationHost;
|
|
1039
1314
|
}
|
|
1040
1315
|
/**
|
|
1041
1316
|
* The documented factory-result contract: EVERY field an extension may
|
|
@@ -1073,6 +1348,13 @@ export interface SigrunExtensionResult extends SigrunLifecycleHandlers {
|
|
|
1073
1348
|
readonly ambientKinds?: readonly string[];
|
|
1074
1349
|
/** Idle workers (the curator contract — `wakeOn` only, `every` retired). */
|
|
1075
1350
|
readonly workers?: readonly ExtensionWorker[];
|
|
1351
|
+
/**
|
|
1352
|
+
* Curation targets (the curator contract, spec P7): this extension's
|
|
1353
|
+
* writable per-user surfaces the curator learns into. Every listed
|
|
1354
|
+
* tool's outside-run calls become review hints (A8's wrapping); a
|
|
1355
|
+
* target without `undo` fails the load.
|
|
1356
|
+
*/
|
|
1357
|
+
readonly curationTargets?: readonly CurationTarget[];
|
|
1076
1358
|
/** Declared jobs (the only time-based scheduler); read-only through the API under `ext:<slug>:<name>` ids. */
|
|
1077
1359
|
readonly jobs?: readonly JobSpec[];
|
|
1078
1360
|
/** HTTP routes mounted at `/v1/ext/<slug>/<path>` (lands in Task 2). */
|