@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
package/dist/extension.d.ts
CHANGED
|
@@ -32,11 +32,14 @@
|
|
|
32
32
|
* validated candidate shape and a compile-time key assertion, so the
|
|
33
33
|
* implementation and this contract cannot drift silently.
|
|
34
34
|
*/
|
|
35
|
-
import type { AssistantMessage, Provider } from "@earendil-works/pi-ai";
|
|
35
|
+
import type { AssistantMessage, Provider, TSchema } from "@earendil-works/pi-ai";
|
|
36
36
|
import type { ConversationDocToken, ConversationId, Cursor, EntryId, EntryQuery, EntryRecord, Extension, InputSubmissionDraft, JsonObject, ModelRef, Page, SubmissionId } from "@earendil-works/pi-durable";
|
|
37
37
|
import type { Access, Sharing } from "@sigrun/shared/access";
|
|
38
|
+
import type { DecisionAnswers, DecisionLimits, DecisionRequest, DecisionResult } from "@sigrun/shared/decisions";
|
|
38
39
|
import type { AmbientPlace, AmbientRecord } from "@sigrun/shared/ambient";
|
|
39
|
-
import type { Action, DataOps, IconName, Tree } from "@sigrun/shared/blocks";
|
|
40
|
+
import type { Action, CoreTarget, DataOps, IconName, Tree } from "@sigrun/shared/blocks";
|
|
41
|
+
import type { UserNotice, UserNoticeInput } from "@sigrun/shared/notices";
|
|
42
|
+
export type { NoticeActionResponse, NoticesResponse, UserNotice, UserNoticeAction, UserNoticeInput, } from "@sigrun/shared/notices";
|
|
40
43
|
import type { InteractionAnswer, InteractionRecord } from "@sigrun/shared/interactions";
|
|
41
44
|
import type { JobSpec, JobWire } from "@sigrun/shared/jobs";
|
|
42
45
|
import type { AccountLinkWire, ConversationSummary, ConversationViewWire, HealthStatus, ModelEntry, SessionMeta, SlashCommand, SubmissionRecordWire, ThinkingLevelWire, UserWire as DirectoryUserWire } from "@sigrun/shared/views";
|
|
@@ -130,6 +133,10 @@ export type ExtensionLifecyclePayload = {
|
|
|
130
133
|
readonly status: "answered" | "skipped" | "expired" | "cancelled";
|
|
131
134
|
/** The answering user (C5 attribution), when a human answered. */
|
|
132
135
|
readonly answeredByUser?: string;
|
|
136
|
+
/** How a DECLINE landed (ask-user Task 1/2): "button" via the decline
|
|
137
|
+
* route/host method, "message" via an admitted decline message. Set
|
|
138
|
+
* only on a declined ("skipped") record. */
|
|
139
|
+
readonly declinedVia?: "button" | "message";
|
|
133
140
|
/** The STORED (masked-by-construction) answer values — render-safe. */
|
|
134
141
|
readonly answer?: Readonly<Record<string, unknown>>;
|
|
135
142
|
readonly owner?: string;
|
|
@@ -272,8 +279,28 @@ export interface CurationTarget {
|
|
|
272
279
|
readonly name: string;
|
|
273
280
|
/** Tools the review may call to write this target; registered by this extension. */
|
|
274
281
|
readonly tools: readonly string[];
|
|
282
|
+
/**
|
|
283
|
+
* Read-only tools the review may call (knowledge C5): model-visible
|
|
284
|
+
* for serving and allowed inside a review, but NEVER wrapped into a
|
|
285
|
+
* review hint and never gated — the loader's wrap feed grows from
|
|
286
|
+
* `tools` only. Registered by this extension; disjoint from `tools`.
|
|
287
|
+
*/
|
|
288
|
+
readonly readTools?: readonly string[];
|
|
275
289
|
/** Routing guidance for one user; `[]` = unavailable for this user. */
|
|
276
290
|
guidance(user: string): readonly string[] | Promise<readonly string[]>;
|
|
291
|
+
/**
|
|
292
|
+
* Whether this target wants the conversation reviewed (the fork path
|
|
293
|
+
* only — mixed-authorship conversations go to the idle digest). Core
|
|
294
|
+
* bounds each call at 2 s; a throw or timeout counts as `false`.
|
|
295
|
+
*/
|
|
296
|
+
wantsReview?(user: string, conversationId: ConversationId): boolean | Promise<boolean>;
|
|
297
|
+
/**
|
|
298
|
+
* Called after a fork review of the conversation COMPLETED (after
|
|
299
|
+
* the result is recorded) — the target's signal that the review
|
|
300
|
+
* consumed its hints. Not called for any other status. Errors are
|
|
301
|
+
* caught and logged; a target must not rely on being called.
|
|
302
|
+
*/
|
|
303
|
+
reviewed?(user: string, conversationId: ConversationId): void | Promise<void>;
|
|
277
304
|
/** Revert one change this target reported. Required. */
|
|
278
305
|
undo(user: string, changeId: string, ctx: HandlerContext): Promise<void>;
|
|
279
306
|
/** Optional idle maintenance, called by the curator's idle pass. */
|
|
@@ -407,6 +434,8 @@ export type CurationEligibility = {
|
|
|
407
434
|
export type CurationTargetInfo = {
|
|
408
435
|
readonly id: string;
|
|
409
436
|
readonly tools: readonly string[];
|
|
437
|
+
/** The target's declared read tools (C5; absent when it has none). */
|
|
438
|
+
readonly readTools?: readonly string[];
|
|
410
439
|
readonly guidance: readonly string[];
|
|
411
440
|
readonly maintain: boolean;
|
|
412
441
|
};
|
|
@@ -451,8 +480,15 @@ export type SigrunCurationHost = {
|
|
|
451
480
|
modelCallsSince(id: ConversationId, entryId?: EntryId): Promise<number>;
|
|
452
481
|
/** Read and clear the conversation's pending review hints (A8's doc). */
|
|
453
482
|
takeHints(id: ConversationId): Promise<readonly CurationHint[]>;
|
|
454
|
-
/** Targets whose tools are all in the conversation's selection (no id: all). */
|
|
483
|
+
/** Targets whose tools (and readTools) are all in the conversation's selection (no id: all). */
|
|
455
484
|
targets(user: string, id?: ConversationId): Promise<readonly CurationTargetInfo[]>;
|
|
485
|
+
/**
|
|
486
|
+
* Whether any target served for `(user, id)` requests a review (C5):
|
|
487
|
+
* `true` when at least one served target's `wantsReview` answers
|
|
488
|
+
* true. Each target's call is bounded (2 s); a throw or timeout
|
|
489
|
+
* counts as `false`. Fork-path only — the idle digest never asks.
|
|
490
|
+
*/
|
|
491
|
+
wantsReview(user: string, id: ConversationId): Promise<boolean>;
|
|
456
492
|
/** The conversation's model-visible tool names (for the allow-list). */
|
|
457
493
|
tools(id: ConversationId): Promise<readonly string[]>;
|
|
458
494
|
/** Fork the conversation at its newest entry and drive the review run. */
|
|
@@ -518,6 +554,40 @@ export interface ProviderReadiness {
|
|
|
518
554
|
* three-state source `/v1/auth/check` reports verbatim.
|
|
519
555
|
*/
|
|
520
556
|
export type ReadinessTracker = () => ProviderReadiness;
|
|
557
|
+
/** One decision model: a bare id plus optional limits narrower than the built-in caps. */
|
|
558
|
+
export interface DecisionModelInfo {
|
|
559
|
+
readonly id: string;
|
|
560
|
+
readonly limits?: DecisionLimits;
|
|
561
|
+
}
|
|
562
|
+
/** The context core stamps on every decision-provider call. */
|
|
563
|
+
export interface DecisionContext {
|
|
564
|
+
/** The bare model id (the `"<provider>/<model>"` spec's second half). */
|
|
565
|
+
readonly model: string;
|
|
566
|
+
/** auth.json[id].apiKey, else the ambient `credentialEnv`; never `"default"`. */
|
|
567
|
+
readonly credential: string | undefined;
|
|
568
|
+
readonly signal: AbortSignal;
|
|
569
|
+
/** The calling extension's slug, core-stamped (`"sigrun"` for the core tool). */
|
|
570
|
+
readonly caller: string;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* One decision-provider contribution: typed questions (noul / choice /
|
|
574
|
+
* score) answered over a JSON state. A factory result carrying only
|
|
575
|
+
* `decisionProviders` is a valid contributor-only result.
|
|
576
|
+
*/
|
|
577
|
+
export interface DecisionProviderContribution {
|
|
578
|
+
/** `^[a-z][a-z0-9-]*$`; the auth.json key and the model prefix. */
|
|
579
|
+
readonly id: string;
|
|
580
|
+
/** Display name. */
|
|
581
|
+
readonly label?: string;
|
|
582
|
+
/** Ambient env fallback core reads when auth.json has no key. */
|
|
583
|
+
readonly credentialEnv?: string;
|
|
584
|
+
/** The fixed model list (restart-required). */
|
|
585
|
+
readonly models: readonly DecisionModelInfo[];
|
|
586
|
+
/** The chat providers' three-state readiness tracker. */
|
|
587
|
+
readonly readiness?: ReadinessTracker;
|
|
588
|
+
/** May throw; core maps a throw to `failed`. */
|
|
589
|
+
evaluate(request: DecisionRequest, ctx: DecisionContext): Promise<DecisionAnswers>;
|
|
590
|
+
}
|
|
521
591
|
/** One provider contribution: a pi-ai `createProvider()` result plus optional boot hints. */
|
|
522
592
|
export interface ProviderContribution {
|
|
523
593
|
readonly provider: Provider;
|
|
@@ -632,6 +702,34 @@ export type RunSettledOutcome = {
|
|
|
632
702
|
readonly status: "unanswered";
|
|
633
703
|
readonly reason: string;
|
|
634
704
|
};
|
|
705
|
+
/**
|
|
706
|
+
* The context a `noticeActions` handler runs with: who clicked, the notice
|
|
707
|
+
* as stored (its `owner` is the handler's own slug), and the dispatch
|
|
708
|
+
* deadline's signal (the 10 s action timeout).
|
|
709
|
+
*/
|
|
710
|
+
export interface UserNoticeActionContext {
|
|
711
|
+
readonly principal: Principal;
|
|
712
|
+
readonly notice: UserNotice;
|
|
713
|
+
readonly signal: AbortSignal;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* What a notice action answers (notices spec §4): `ok` clears the notice
|
|
717
|
+
* unless `keep: true`; `message` shows in the client, `navigate` moves it
|
|
718
|
+
* to a core target. A failure keeps the notice and shows the error.
|
|
719
|
+
*/
|
|
720
|
+
export type UserNoticeActionResult = {
|
|
721
|
+
readonly ok: true;
|
|
722
|
+
readonly keep?: boolean;
|
|
723
|
+
readonly message?: string;
|
|
724
|
+
readonly navigate?: CoreTarget;
|
|
725
|
+
} | {
|
|
726
|
+
readonly ok: false;
|
|
727
|
+
readonly error: {
|
|
728
|
+
readonly message: string;
|
|
729
|
+
};
|
|
730
|
+
};
|
|
731
|
+
/** One declared notice action handler (the `noticeActions` value shape). */
|
|
732
|
+
export type UserNoticeActionHandler = (ctx: UserNoticeActionContext, payload: JsonObject) => Promise<UserNoticeActionResult>;
|
|
635
733
|
/** One explicit notification an extension sends through `host.notify`. */
|
|
636
734
|
export interface ExtensionNoticeInput {
|
|
637
735
|
/** A directory user; `undefined` only when the directory is empty. */
|
|
@@ -987,6 +1085,12 @@ export interface SurfaceDef<P extends "view" | "panel" | "settings"> {
|
|
|
987
1085
|
readonly placement: P;
|
|
988
1086
|
/** Data is identical for every principal with access (§7.3). */
|
|
989
1087
|
readonly sharedData?: boolean;
|
|
1088
|
+
/** Panels only: the channels attach this panel with
|
|
1089
|
+
* `params: { conversationId }` of the OPEN conversation, so one
|
|
1090
|
+
* instance follows the conversation the user is in. The id is
|
|
1091
|
+
* client-supplied — the extension must check the caller's access to
|
|
1092
|
+
* that conversation before rendering or acting on it. */
|
|
1093
|
+
readonly perConversation?: boolean;
|
|
990
1094
|
/** Channels refetch data on this interval while visible (min 5000). */
|
|
991
1095
|
readonly refreshMs?: number;
|
|
992
1096
|
/** §7.3; default: canonical JSON of params. The principal argument
|
|
@@ -1133,6 +1237,15 @@ export interface SigrunHost {
|
|
|
1133
1237
|
}): Promise<boolean>;
|
|
1134
1238
|
/** Mark a pending interaction expired/cancelled/skipped (extension-owned semantics). */
|
|
1135
1239
|
settleInteraction(conversationId: ConversationId, callId: string, status: "skipped" | "expired" | "cancelled", context: HandlerContext): Promise<boolean>;
|
|
1240
|
+
/**
|
|
1241
|
+
* Decline a pending DECLINABLE interaction as a human (ask-user Task
|
|
1242
|
+
* 1): status "skipped", `declinedVia: "button"`. `opts.user` names
|
|
1243
|
+
* the declining human (the same attribution `answerInteraction`
|
|
1244
|
+
* stamps); false when the record is not pending or not declinable.
|
|
1245
|
+
*/
|
|
1246
|
+
declineInteraction(conversationId: ConversationId, callId: string, context: HandlerContext, opts?: {
|
|
1247
|
+
readonly user?: string;
|
|
1248
|
+
}): Promise<boolean>;
|
|
1136
1249
|
/**
|
|
1137
1250
|
* Inspect one interaction record WITHOUT consuming anything: returns
|
|
1138
1251
|
* the same stored projection as {@link SigrunHost.snapshotInteraction}
|
|
@@ -1288,6 +1401,33 @@ export interface SigrunHost {
|
|
|
1288
1401
|
/** Read-only job lookup; retired and unknown ids resolve to `undefined`. */
|
|
1289
1402
|
readonly jobs: {
|
|
1290
1403
|
get(id: string): Promise<HostJobInfo | undefined>;
|
|
1404
|
+
/**
|
|
1405
|
+
* The job whose fire is CURRENTLY running in this conversation:
|
|
1406
|
+
* the job with `lastRun.outcome === "running"` and
|
|
1407
|
+
* `lastRun.conversationId === conversationId` — `undefined` when no
|
|
1408
|
+
* job fire is running there. `owner` is the EFFECTIVE owner (the
|
|
1409
|
+
* same value `get` returns: an ownerless or orphaned job resolves
|
|
1410
|
+
* to the primary admin). Two matching runs resolve to the newest
|
|
1411
|
+
* `startedAt`.
|
|
1412
|
+
*/
|
|
1413
|
+
activeRun(conversationId: ConversationId): Promise<{
|
|
1414
|
+
readonly jobId: string;
|
|
1415
|
+
readonly owner: string | undefined;
|
|
1416
|
+
} | undefined>;
|
|
1417
|
+
/**
|
|
1418
|
+
* Whether the conversation's CURRENT run is job-driven (ask-user
|
|
1419
|
+
* Task 3, spec §6): true when the session meta carries a `jobId`
|
|
1420
|
+
* (a `conversation: "new"` run), or any input of the conversation's
|
|
1421
|
+
* current run (`LiveDoc` `run.inputs`, present exactly while busy)
|
|
1422
|
+
* resolves to a submission whose requestId carries the job prefix
|
|
1423
|
+
* (`job:<fireKey>`, the prefix `jobs/fire.ts` stamps on both fire
|
|
1424
|
+
* shapes). Interactive tools call this in execute and refuse with an
|
|
1425
|
+
* error telling the model to escalate instead — the execute-time
|
|
1426
|
+
* guard for the conversations the creation-time filter cannot reach
|
|
1427
|
+
* (a job firing into an existing conversation; defence in depth
|
|
1428
|
+
* should the filter ever miss).
|
|
1429
|
+
*/
|
|
1430
|
+
isJobRun(conversationId: ConversationId, context: HandlerContext): Promise<boolean>;
|
|
1291
1431
|
};
|
|
1292
1432
|
/**
|
|
1293
1433
|
* Send one explicit notice through every registered notifier. Validation
|
|
@@ -1295,6 +1435,29 @@ export interface SigrunHost {
|
|
|
1295
1435
|
* lane and this resolves without awaiting notifier completion.
|
|
1296
1436
|
*/
|
|
1297
1437
|
notify(input: ExtensionNoticeInput): Promise<void>;
|
|
1438
|
+
/**
|
|
1439
|
+
* The per-user notices channel (notices spec §2): post, clear and list
|
|
1440
|
+
* THIS extension's notices. `post` validates the input (fail-loud — a
|
|
1441
|
+
* rejection throws `Error("<field>: <message>")` naming the offending
|
|
1442
|
+
* field), checks the recipient exists and can see every conversation
|
|
1443
|
+
* the notice names (`conversationId` and any `navigate` conversation
|
|
1444
|
+
* target), and stores it. `clear` is owner-scoped: another extension's
|
|
1445
|
+
* notice is never touched. `list` reads only this extension's notices
|
|
1446
|
+
* for the user.
|
|
1447
|
+
*/
|
|
1448
|
+
readonly notices: {
|
|
1449
|
+
post(input: UserNoticeInput): Promise<{
|
|
1450
|
+
id: string;
|
|
1451
|
+
}>;
|
|
1452
|
+
clear(selector: {
|
|
1453
|
+
user: string;
|
|
1454
|
+
key: string;
|
|
1455
|
+
} | {
|
|
1456
|
+
user: string;
|
|
1457
|
+
id: string;
|
|
1458
|
+
}): Promise<void>;
|
|
1459
|
+
list(user: string): Promise<readonly UserNotice[]>;
|
|
1460
|
+
};
|
|
1298
1461
|
/** The user directory (admin wire form; emails are admin material). */
|
|
1299
1462
|
readonly users: {
|
|
1300
1463
|
list(): Promise<readonly DirectoryUserWire[]>;
|
|
@@ -1338,10 +1501,54 @@ export interface SigrunHost {
|
|
|
1338
1501
|
};
|
|
1339
1502
|
/**
|
|
1340
1503
|
* The served model catalog (gateway C4b): the SAME list `GET
|
|
1341
|
-
* /v1/models` serves (picker entries — no provider internals)
|
|
1504
|
+
* /v1/models` serves (picker entries — no provider internals), plus
|
|
1505
|
+
* one-shot completions with NO conversation (warden Task 4 —
|
|
1506
|
+
* skill-card generation is the first consumer).
|
|
1342
1507
|
*/
|
|
1343
1508
|
readonly models: {
|
|
1344
1509
|
list(): Promise<readonly ModelEntry[]>;
|
|
1510
|
+
/**
|
|
1511
|
+
* Run ONE one-shot completion and return its concatenated text
|
|
1512
|
+
* parts (non-text parts skipped) plus the `provider/id` of the
|
|
1513
|
+
* model actually used. Model resolution: `input.model` wins;
|
|
1514
|
+
* else the live settings' `namingModel` when it is a string
|
|
1515
|
+
* (`false` counts as unset — the utility role's binding); else
|
|
1516
|
+
* the boot default model. THROWS (never resolves silently) on an
|
|
1517
|
+
* unserved model (naming it), a timeout (default 60 000 ms), or
|
|
1518
|
+
* a `stopReason` of `error`/`aborted`.
|
|
1519
|
+
*/
|
|
1520
|
+
complete(input: {
|
|
1521
|
+
readonly model?: string;
|
|
1522
|
+
readonly role?: "utility";
|
|
1523
|
+
readonly systemPrompt?: string;
|
|
1524
|
+
readonly prompt: string;
|
|
1525
|
+
readonly timeoutMs?: number;
|
|
1526
|
+
readonly signal?: AbortSignal;
|
|
1527
|
+
}): Promise<{
|
|
1528
|
+
readonly text: string;
|
|
1529
|
+
readonly model: string;
|
|
1530
|
+
}>;
|
|
1531
|
+
};
|
|
1532
|
+
/**
|
|
1533
|
+
* Decision providers (warden Task 5 — the decision-providers spec):
|
|
1534
|
+
* typed questions (noul / choice / score) answered over a JSON state.
|
|
1535
|
+
* `evaluate` NEVER throws; failures arrive as the `ok: false` half of
|
|
1536
|
+
* {@link DecisionResult}. Model resolution: `opts.model` wins; else
|
|
1537
|
+
* the live `decisionModel` setting; else the first model of the
|
|
1538
|
+
* first-registered decision provider.
|
|
1539
|
+
*/
|
|
1540
|
+
readonly decisions: {
|
|
1541
|
+
evaluate(request: DecisionRequest, opts?: {
|
|
1542
|
+
model?: string;
|
|
1543
|
+
signal?: AbortSignal;
|
|
1544
|
+
timeoutMs?: number;
|
|
1545
|
+
}): Promise<DecisionResult>;
|
|
1546
|
+
/** Every decision model, with its provider's readiness status. */
|
|
1547
|
+
list(): Promise<readonly {
|
|
1548
|
+
model: string;
|
|
1549
|
+
provider: string;
|
|
1550
|
+
status: ProviderReadiness["status"];
|
|
1551
|
+
}[]>;
|
|
1345
1552
|
};
|
|
1346
1553
|
/**
|
|
1347
1554
|
* The slash-command surface (gateway C4b): the merged catalog for
|
|
@@ -1373,6 +1580,28 @@ export interface SigrunHost {
|
|
|
1373
1580
|
* for the bridge, never a silent drop.
|
|
1374
1581
|
*/
|
|
1375
1582
|
readonly curation: SigrunCurationHost;
|
|
1583
|
+
/**
|
|
1584
|
+
* The published-hook surface (knowledge C7): look up a hook another
|
|
1585
|
+
* extension published (`get` returns `undefined` while loading is
|
|
1586
|
+
* still running and for unowned names) or call it through the core
|
|
1587
|
+
* registry. `call` NEVER throws: an unowned name is `absent`, a
|
|
1588
|
+
* payload the owner's schema rejects is `rejected`, and a provider
|
|
1589
|
+
* failure is `failed` with a reason. A call issued before loading
|
|
1590
|
+
* finishes waits for it — never `await` one inside a factory body
|
|
1591
|
+
* (stash the host; call from a tool, handler, worker, or job).
|
|
1592
|
+
*/
|
|
1593
|
+
readonly publishedHooks: {
|
|
1594
|
+
get(name: string): {
|
|
1595
|
+
provider: string;
|
|
1596
|
+
input: TSchema;
|
|
1597
|
+
output?: TSchema;
|
|
1598
|
+
} | undefined;
|
|
1599
|
+
call(name: string, payload: unknown, opts?: {
|
|
1600
|
+
user?: string;
|
|
1601
|
+
conversationId?: ConversationId;
|
|
1602
|
+
abortSignal?: AbortSignal;
|
|
1603
|
+
}): Promise<PublishedHookResult>;
|
|
1604
|
+
};
|
|
1376
1605
|
}
|
|
1377
1606
|
/**
|
|
1378
1607
|
* The documented factory-result contract: EVERY field an extension may
|
|
@@ -1396,6 +1625,8 @@ export interface SigrunExtensionResult extends SigrunLifecycleHandlers {
|
|
|
1396
1625
|
readonly name?: string;
|
|
1397
1626
|
/** Provider contributions, merged into the boot model catalog (last-registered-wins, collisions recorded). */
|
|
1398
1627
|
readonly providers?: readonly ProviderContribution[];
|
|
1628
|
+
/** Decision-provider contributions (warden Task 5); last-registered wins per id. */
|
|
1629
|
+
readonly decisionProviders?: readonly DecisionProviderContribution[];
|
|
1399
1630
|
/** External identity providers; core qualifies ids as `login:<slug>:<id>`. */
|
|
1400
1631
|
readonly loginProviders?: readonly LoginProviderContribution[];
|
|
1401
1632
|
/** Webui-bundle contributions; each `dir` anchors at the extension ENTRY's directory. */
|
|
@@ -1406,6 +1637,17 @@ export interface SigrunExtensionResult extends SigrunLifecycleHandlers {
|
|
|
1406
1637
|
readonly runSettled?: ExtensionRunSettledHandler;
|
|
1407
1638
|
/** Declared interaction kinds (the seam's write gate + capability inventory). */
|
|
1408
1639
|
readonly interactionKinds?: readonly string[];
|
|
1640
|
+
/**
|
|
1641
|
+
* Tool names this extension registers that need a human present
|
|
1642
|
+
* (ask-user Task 3, spec §6). Validated at load like
|
|
1643
|
+
* `interactionKinds` (each name must name a tool this extension's
|
|
1644
|
+
* `extension.tools` registers; a bad declaration quarantines). Core
|
|
1645
|
+
* hides the named tools in every non-interactive run (job run
|
|
1646
|
+
* conversations, curator runs); in a job-fired EXISTING conversation
|
|
1647
|
+
* the tool stays listed and must refuse at execute time — guard with
|
|
1648
|
+
* `host.jobs.isJobRun`.
|
|
1649
|
+
*/
|
|
1650
|
+
readonly interactiveTools?: readonly string[];
|
|
1409
1651
|
/** Declared ambient surface keys (the seam's write gate + capability inventory). */
|
|
1410
1652
|
readonly ambientKinds?: readonly string[];
|
|
1411
1653
|
/** Idle workers (the curator contract — `wakeOn` only, `every` retired). */
|
|
@@ -1417,6 +1659,15 @@ export interface SigrunExtensionResult extends SigrunLifecycleHandlers {
|
|
|
1417
1659
|
* target without `undo` fails the load.
|
|
1418
1660
|
*/
|
|
1419
1661
|
readonly curationTargets?: readonly CurationTarget[];
|
|
1662
|
+
/**
|
|
1663
|
+
* Declared notice action handlers (notices Task 3): the names a posted
|
|
1664
|
+
* notice's `extension` actions may name, keyed by handler name
|
|
1665
|
+
* (`/^[A-Za-z][A-Za-z0-9-]{0,31}$/`). A malformed entry quarantines the
|
|
1666
|
+
* extension at load.
|
|
1667
|
+
*/
|
|
1668
|
+
readonly noticeActions?: {
|
|
1669
|
+
readonly [name: string]: UserNoticeActionHandler;
|
|
1670
|
+
};
|
|
1420
1671
|
/** Declared jobs (the only time-based scheduler); read-only through the API under `ext:<slug>:<name>` ids. */
|
|
1421
1672
|
readonly jobs?: readonly JobSpec[];
|
|
1422
1673
|
/** HTTP routes mounted at `/v1/ext/<slug>/<path>` (lands in Task 2). */
|
|
@@ -1444,6 +1695,82 @@ export interface SigrunExtensionResult extends SigrunLifecycleHandlers {
|
|
|
1444
1695
|
readonly gateway?: GatewayEngine;
|
|
1445
1696
|
/** Declared surfaces — views and panels (UI-blocks U3, spec §7.1; the service lands in Task 2). */
|
|
1446
1697
|
readonly surfaces?: SurfacesContribution;
|
|
1698
|
+
/**
|
|
1699
|
+
* Published hooks (knowledge C7): named, schema-validated calls this
|
|
1700
|
+
* extension offers OTHER extensions through `host.publishedHooks`.
|
|
1701
|
+
* Core compiles each schema once at load; a malformed declaration
|
|
1702
|
+
* (bad name, duplicate within the extension, non-object input,
|
|
1703
|
+
* non-function `run`, a schema that fails to compile) quarantines
|
|
1704
|
+
* the extension. The first configured provider of a name owns it;
|
|
1705
|
+
* configured providers beat built-ins.
|
|
1706
|
+
*/
|
|
1707
|
+
readonly publishedHooks?: readonly PublishedHook[];
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* A published hook (knowledge plan, Task 1): a named, schema-validated
|
|
1711
|
+
* call one extension publishes for OTHER extensions to invoke through
|
|
1712
|
+
* the core registry (the extension-to-extension seam). `name` must
|
|
1713
|
+
* match `/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/` (at least one dot,
|
|
1714
|
+
* at most 128 chars). `run` receives the validated payload plus the
|
|
1715
|
+
* {@link PublishedHookCall} context and resolves with the result.
|
|
1716
|
+
*/
|
|
1717
|
+
export interface PublishedHook {
|
|
1718
|
+
readonly name: string;
|
|
1719
|
+
readonly description?: string;
|
|
1720
|
+
readonly input: TSchema;
|
|
1721
|
+
readonly output?: TSchema;
|
|
1722
|
+
run(payload: unknown, call: PublishedHookCall): Promise<unknown>;
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* The call context a published hook's `run` sees: the calling extension's
|
|
1726
|
+
* name plus the resolved identity. `user` comes from the conversation's
|
|
1727
|
+
* acting user when a `conversationId` is given, else the caller's
|
|
1728
|
+
* declared user; a caller-declared user that disagrees with the
|
|
1729
|
+
* conversation owner is rejected before the provider ever runs.
|
|
1730
|
+
*/
|
|
1731
|
+
export interface PublishedHookCall {
|
|
1732
|
+
readonly caller: string;
|
|
1733
|
+
readonly user: string | undefined;
|
|
1734
|
+
readonly conversationId: ConversationId | undefined;
|
|
1735
|
+
readonly abortSignal: AbortSignal | undefined;
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* The outcome of one registry call. `absent` — no provider owns the
|
|
1739
|
+
* name. `ok` — the provider ran and produced a valid result. `rejected`
|
|
1740
|
+
* — the call never ran (oversize/unstringifiable payload, schema
|
|
1741
|
+
* mismatch, or identity disagreement). `failed` — the provider ran (or
|
|
1742
|
+
* was interrupted) and could not deliver: `threw`, `timeout`, `aborted`,
|
|
1743
|
+
* `bad-output`, `too-large`, or `too-deep` (depth bound).
|
|
1744
|
+
*/
|
|
1745
|
+
export type PublishedHookResult = {
|
|
1746
|
+
status: "absent";
|
|
1747
|
+
} | {
|
|
1748
|
+
status: "ok";
|
|
1749
|
+
provider: string;
|
|
1750
|
+
value: unknown;
|
|
1751
|
+
} | {
|
|
1752
|
+
status: "rejected";
|
|
1753
|
+
provider: string;
|
|
1754
|
+
errors: {
|
|
1755
|
+
path: string;
|
|
1756
|
+
message: string;
|
|
1757
|
+
}[];
|
|
1758
|
+
} | {
|
|
1759
|
+
status: "failed";
|
|
1760
|
+
provider: string;
|
|
1761
|
+
reason: "threw" | "timeout" | "aborted" | "bad-output" | "too-large" | "too-deep";
|
|
1762
|
+
error: string;
|
|
1763
|
+
};
|
|
1764
|
+
/**
|
|
1765
|
+
* One extension's declaration of a published hook, in loader order.
|
|
1766
|
+
* `builtin` marks core-bundled extensions: when a configured extension
|
|
1767
|
+
* shadows a built-in the warning names replacing the built-in instead
|
|
1768
|
+
* of reordering the extension list.
|
|
1769
|
+
*/
|
|
1770
|
+
export interface PublishedHookDeclaration {
|
|
1771
|
+
readonly extension: string;
|
|
1772
|
+
readonly builtin: boolean;
|
|
1773
|
+
readonly hook: PublishedHook;
|
|
1447
1774
|
}
|
|
1448
1775
|
/**
|
|
1449
1776
|
* The extension factory: sigrun calls it at load, passing the host
|
package/dist/harness/boot.js
CHANGED
|
@@ -11,7 +11,7 @@ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { Harness, } from "@earendil-works/pi-durable";
|
|
13
13
|
import { openNodeSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/node";
|
|
14
|
-
import { effectiveMethods } from "../config.js";
|
|
14
|
+
import { DECISION_TIMEOUT_ENV, effectiveMethods } from "../config.js";
|
|
15
15
|
import { requiresProxyVouch } from "../server/auth/proxy-only.js";
|
|
16
16
|
import { wireActivity } from "./activity.js";
|
|
17
17
|
import { snapshotBeforeMigration } from "./migration-snapshot.js";
|
|
@@ -29,6 +29,9 @@ import { AuthStore } from "./auth.js";
|
|
|
29
29
|
import { openIdentityStores, ensureSetupCode, } from "./identity/index.js";
|
|
30
30
|
import { MEMORY_SECTION_KEY, noopMemoryExtension } from "./memory-default.js";
|
|
31
31
|
import { createSeamHost, } from "./seam-host.js";
|
|
32
|
+
import { buildPublishedHookRegistry, } from "./published-hooks.js";
|
|
33
|
+
import { buildDecisionRegistry, } from "./decisions.js";
|
|
34
|
+
import { buildDecisionEvaluateTool, createDecisionsExtension, } from "./decision-tool.js";
|
|
32
35
|
import { contextFilesExtension } from "./sections/context-files.js";
|
|
33
36
|
import { createJobStateExtension } from "./sections/job-state.js";
|
|
34
37
|
import { modelGuidanceExtension } from "./sections/model-guidance.js";
|
|
@@ -653,6 +656,15 @@ export async function openHarness(config, options = {}) {
|
|
|
653
656
|
// can show and update interactive block surfaces in chat — the same
|
|
654
657
|
// core-owned slot as the coding/search tools, never quarantined.
|
|
655
658
|
registry.install(blocksToolsExtension);
|
|
659
|
+
// The decision tool (warden Task 7): installed HERE with an empty
|
|
660
|
+
// tool list — the placeholder holding the core `sigrun.decisions`
|
|
661
|
+
// name — because the decision declarations are only known AFTER the
|
|
662
|
+
// loader runs. Once the registry is built (below), boot RE-INSTALLS
|
|
663
|
+
// the extension with `decision_evaluate` when the registry lists ≥ 1
|
|
664
|
+
// decision model (`registry.install` replaces a same-name extension
|
|
665
|
+
// in place); a no-provider boot keeps the placeholder and the tool
|
|
666
|
+
// is never offered.
|
|
667
|
+
registry.install(createDecisionsExtension());
|
|
656
668
|
// Extension packages load before `Harness.open` reads the registry
|
|
657
669
|
// (spec §3 boot order). Failures quarantine and boot continues. §3
|
|
658
670
|
// factory-result producers are retained alongside the outcomes (Task 9e).
|
|
@@ -732,6 +744,31 @@ export async function openHarness(config, options = {}) {
|
|
|
732
744
|
});
|
|
733
745
|
const bindHarness = (harness) => release(harness);
|
|
734
746
|
const harnessOf = () => harnessPromise;
|
|
747
|
+
// The published-hook registry slot (knowledge C7): the deferred MUST
|
|
748
|
+
// exist before `loadExtensions` runs — seam hosts are built inside the
|
|
749
|
+
// loader's hostContextFactory and close over this slot. Boot builds
|
|
750
|
+
// the registry from the loader's declarations right after
|
|
751
|
+
// `loadExtensions` returns and resolves the deferred; a
|
|
752
|
+
// `host.publishedHooks.call` issued during loading waits on it
|
|
753
|
+
// (the harnessPromise pattern), a `get` before then reads
|
|
754
|
+
// `undefined`.
|
|
755
|
+
let publishRegistryReady;
|
|
756
|
+
const publishedHooksReady = new Promise((resolve) => {
|
|
757
|
+
publishRegistryReady = resolve;
|
|
758
|
+
});
|
|
759
|
+
const publishedHooksSlot = { registry: undefined };
|
|
760
|
+
// The decision-registry slot (warden Task 6): the SAME deferred
|
|
761
|
+
// discipline as the published-hook slot above — seam hosts are built
|
|
762
|
+
// inside the loader's hostContextFactory (before the registry can
|
|
763
|
+
// exist) and close over this slot. Boot builds the registry from the
|
|
764
|
+
// loader's declarations right after `loadExtensions` returns and
|
|
765
|
+
// resolves the deferred; a `host.decisions.evaluate` issued from a
|
|
766
|
+
// factory body waits on it.
|
|
767
|
+
let decisionRegistryReady;
|
|
768
|
+
const decisionsReady = new Promise((resolve) => {
|
|
769
|
+
decisionRegistryReady = resolve;
|
|
770
|
+
});
|
|
771
|
+
const decisionsSlot = { registry: undefined };
|
|
735
772
|
// The boot-shared store trio (Task 5): one SecretsStore + one
|
|
736
773
|
// ExtensionSettingsStore over the roots resolved at the top of boot;
|
|
737
774
|
// each seam host scopes it to its extension's slug (cheap, no I/O —
|
|
@@ -741,7 +778,7 @@ export async function openHarness(config, options = {}) {
|
|
|
741
778
|
const sharedStores = sharedExtensionStores(roots, env, identity.users);
|
|
742
779
|
const seamHosts = new Map();
|
|
743
780
|
const seamKinds = new Map();
|
|
744
|
-
const { outcomes: extensionOutcomes, healthProducers: extensionHealth, runSettledBindings, settingsChangedBindings, lifecycleBindings, workerBindings, curationBindings, userServices: userServiceBindings, providerContributors, loginProviders, webuiBundles: extensionWebuiBundles, skillRoots: extensionSkillRoots, jobBindings, routeBindings, commandBindings, notifierBindings, channelBindings, gatewayEngine, surfaces: surfaceBindings, settingsSections: settingsSectionBindings, perUserSlugs: loadedPerUserSlugs, } = await loadExtensions(loaderInputs, registry, {
|
|
781
|
+
const { outcomes: extensionOutcomes, healthProducers: extensionHealth, runSettledBindings, settingsChangedBindings, lifecycleBindings, workerBindings, curationBindings, noticeActionBindings, publishedHookDeclarations, decisionProviderDeclarations, userServices: userServiceBindings, providerContributors, loginProviders, webuiBundles: extensionWebuiBundles, skillRoots: extensionSkillRoots, jobBindings, interactiveTools: loadedInteractiveTools, routeBindings, commandBindings, notifierBindings, channelBindings, gatewayEngine, surfaces: surfaceBindings, settingsSections: settingsSectionBindings, perUserSlugs: loadedPerUserSlugs, } = await loadExtensions(loaderInputs, registry, {
|
|
745
782
|
packagesDir,
|
|
746
783
|
// Curator A8 (P9): the wrap feed — the loader grows it with each
|
|
747
784
|
// validated target tool and wraps those tools at their install.
|
|
@@ -764,7 +801,10 @@ export async function openHarness(config, options = {}) {
|
|
|
764
801
|
}, sharedStores, {
|
|
765
802
|
declared: (slug) => perUserSlugs.includes(slug),
|
|
766
803
|
set: (slug, user, tools) => userTools.set(slug, user, tools),
|
|
767
|
-
}, userServices
|
|
804
|
+
}, userServices, {
|
|
805
|
+
publishedHooks: { current: publishedHooksSlot, ready: publishedHooksReady },
|
|
806
|
+
decisions: { current: decisionsSlot, ready: decisionsReady },
|
|
807
|
+
});
|
|
768
808
|
host.rekey({ name: extensionName }, kinds);
|
|
769
809
|
// File under the final name only, and only once the load
|
|
770
810
|
// resolved: the PRE-import call must NOT file (its identity is
|
|
@@ -797,6 +837,65 @@ export async function openHarness(config, options = {}) {
|
|
|
797
837
|
// wrapping feed (isCurationTool) and the A9 host.curation surface
|
|
798
838
|
// read it. An empty fold is the no-declarer deploy.
|
|
799
839
|
const curation = buildCurationRegistry(curationBindings);
|
|
840
|
+
// The published-hook registry (knowledge C7): built from the loader's
|
|
841
|
+
// collected declarations the moment `loadExtensions` returns, then the
|
|
842
|
+
// deferred resolves — a `host.publishedHooks.call` issued from a
|
|
843
|
+
// factory body (still in flight) proceeds from here. `actingUser`
|
|
844
|
+
// reads through the harness deferred: a call whose conversation
|
|
845
|
+
// identity resolves during the load window waits for `Harness.open`
|
|
846
|
+
// exactly like the other bridged members. Load order between caller
|
|
847
|
+
// and provider never matters — lookup is at call time.
|
|
848
|
+
publishedHooksSlot.registry = buildPublishedHookRegistry(publishedHookDeclarations, {
|
|
849
|
+
actingUser: async (id) => {
|
|
850
|
+
const h = await harnessPromise;
|
|
851
|
+
return (await h.snapshot(SessionMetaDoc, id, BACKGROUND_CONTEXT))
|
|
852
|
+
?.owner;
|
|
853
|
+
},
|
|
854
|
+
deadlineMs: config.publishedHookDeadlineMs,
|
|
855
|
+
warn: (message) => console.warn(message),
|
|
856
|
+
});
|
|
857
|
+
// The decision registry (warden Task 6): built from the loader's
|
|
858
|
+
// collected declarations the moment `loadExtensions` returns (the
|
|
859
|
+
// published-hook pattern above), then the deferred resolves — a
|
|
860
|
+
// `host.decisions.evaluate` issued from a factory body (still in
|
|
861
|
+
// flight) proceeds from here. The credential resolves PER CALL:
|
|
862
|
+
// `auth.json[id].apiKey` (a live read, so a key written through the
|
|
863
|
+
// providers route applies without a restart) wins over the ambient
|
|
864
|
+
// `credentialEnv` with the auth bridge's `""`-means-unset rule; the
|
|
865
|
+
// magic `"default"` key never applies (it names the default CHAT
|
|
866
|
+
// provider). The settings read is live too: `decisionModel` and
|
|
867
|
+
// `server.decisions.timeoutMs` apply without a restart.
|
|
868
|
+
decisionsSlot.registry = buildDecisionRegistry(decisionProviderDeclarations, {
|
|
869
|
+
credential: (id, credentialEnv) => {
|
|
870
|
+
const stored = authStore.read()[id]?.apiKey;
|
|
871
|
+
if (stored !== undefined)
|
|
872
|
+
return stored;
|
|
873
|
+
return credentialEnv === undefined
|
|
874
|
+
? undefined
|
|
875
|
+
: env[credentialEnv]?.trim() || undefined;
|
|
876
|
+
},
|
|
877
|
+
readSettings: () => settingsStore.read(),
|
|
878
|
+
timeoutCapMs: config.decisions.timeoutMs,
|
|
879
|
+
// The env tier of the timeout knob: env always wins over a live
|
|
880
|
+
// file edit (the config's env-over-file precedence, per call).
|
|
881
|
+
...(env[DECISION_TIMEOUT_ENV] === undefined || env[DECISION_TIMEOUT_ENV] === ""
|
|
882
|
+
? {}
|
|
883
|
+
: { timeoutMs: config.decisions.timeoutMs }),
|
|
884
|
+
now: () => Date.now(),
|
|
885
|
+
warn: (message) => console.warn(message),
|
|
886
|
+
});
|
|
887
|
+
decisionRegistryReady();
|
|
888
|
+
// Warden Task 7: the late fill — swap the placeholder extension for
|
|
889
|
+
// the tool-bearing one when any decision model exists at boot.
|
|
890
|
+
if (decisionsSlot.registry !== undefined && decisionsSlot.registry.list().length > 0) {
|
|
891
|
+
registry.install(createDecisionsExtension([
|
|
892
|
+
buildDecisionEvaluateTool({
|
|
893
|
+
decisions: decisionsSlot.registry,
|
|
894
|
+
settings: settingsStore,
|
|
895
|
+
}),
|
|
896
|
+
]));
|
|
897
|
+
}
|
|
898
|
+
publishRegistryReady();
|
|
800
899
|
for (const binding of userServiceBindings) {
|
|
801
900
|
userServices.register(binding.extension, binding.services, binding.packageDir);
|
|
802
901
|
}
|
|
@@ -925,6 +1024,12 @@ export async function openHarness(config, options = {}) {
|
|
|
925
1024
|
// `typed` entry — there is no user turn to attribute).
|
|
926
1025
|
commandExpansion,
|
|
927
1026
|
envBuilder: undefined,
|
|
1027
|
+
// Ask-user Task 3: job fires hide interactive tools (assigned with
|
|
1028
|
+
// the rest of the late-bound deps after the load completes).
|
|
1029
|
+
// SAFETY: the placeholder is assigned below, before any fireJob
|
|
1030
|
+
// caller can run (the same late-binding pattern as `harness`/
|
|
1031
|
+
// `storage`/`envBuilder` on this deps object).
|
|
1032
|
+
interactiveTools: undefined,
|
|
928
1033
|
// Gateway C7: a deliver-carrying fire warns once per run when no
|
|
929
1034
|
// engine loaded (core ships none — `gatewayEngine` is undefined
|
|
930
1035
|
// unless an extension declared one).
|
|
@@ -1176,6 +1281,7 @@ export async function openHarness(config, options = {}) {
|
|
|
1176
1281
|
// JobsService — happens after this point.
|
|
1177
1282
|
jobRuntimeDeps.harness = harness;
|
|
1178
1283
|
jobRuntimeDeps.storage = storage;
|
|
1284
|
+
jobRuntimeDeps.interactiveTools = new Set(loadedInteractiveTools);
|
|
1179
1285
|
// Drain the restart-limit parks reported during open before scheduling
|
|
1180
1286
|
// resumes. A failed status patch is logged and dropped by applyParkedJobs.
|
|
1181
1287
|
await applyParkedJobs(harness, reports.takeParked(), BACKGROUND_CONTEXT);
|
|
@@ -1252,6 +1358,7 @@ export async function openHarness(config, options = {}) {
|
|
|
1252
1358
|
storage,
|
|
1253
1359
|
curation,
|
|
1254
1360
|
defaultModel,
|
|
1361
|
+
interactiveTools: new Set(loadedInteractiveTools),
|
|
1255
1362
|
stores: curationOf,
|
|
1256
1363
|
skillsOptions: {
|
|
1257
1364
|
userDir: join(roots.configDir, "skills"),
|
|
@@ -1496,6 +1603,7 @@ export async function openHarness(config, options = {}) {
|
|
|
1496
1603
|
reports,
|
|
1497
1604
|
jobRunTotals: () => jobRunTotals,
|
|
1498
1605
|
defaultModel,
|
|
1606
|
+
interactiveTools: new Set(loadedInteractiveTools),
|
|
1499
1607
|
providerCollisions: sink.collisions,
|
|
1500
1608
|
webuiBundles,
|
|
1501
1609
|
webuiBundleCollisions,
|
|
@@ -1511,6 +1619,7 @@ export async function openHarness(config, options = {}) {
|
|
|
1511
1619
|
identity,
|
|
1512
1620
|
memoryHealth,
|
|
1513
1621
|
settings: settingsStore,
|
|
1622
|
+
decisions: decisionsSlot.registry,
|
|
1514
1623
|
extensionStores: sharedStores,
|
|
1515
1624
|
packagesDir,
|
|
1516
1625
|
seamHosts,
|
|
@@ -1525,6 +1634,7 @@ export async function openHarness(config, options = {}) {
|
|
|
1525
1634
|
},
|
|
1526
1635
|
extensionRoutes: routeBindings,
|
|
1527
1636
|
extensionCommands: commandBindings,
|
|
1637
|
+
noticeActionBindings,
|
|
1528
1638
|
extensionSurfaces: surfaceBindings,
|
|
1529
1639
|
extensionSettingsSections: settingsSectionBindings,
|
|
1530
1640
|
notifierBindings,
|
|
@@ -1534,6 +1644,7 @@ export async function openHarness(config, options = {}) {
|
|
|
1534
1644
|
curation,
|
|
1535
1645
|
curationHost,
|
|
1536
1646
|
curationOf,
|
|
1647
|
+
publishedHooks: () => publishedHooksSlot.registry?.listing() ?? [],
|
|
1537
1648
|
workspaceRootOf: (owner) => workspaceService?.workspaceRoot(owner),
|
|
1538
1649
|
history: { workspace: workspaceHistoryOf },
|
|
1539
1650
|
commandExpansion,
|