@avocadostudio-ai/orchestrator-core 0.22.0 → 0.23.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/dist/agent/agent-loop-openai.js +3 -1
- package/dist/agent/agent-provider.js +2 -2
- package/dist/agents/backtest.d.ts +35 -0
- package/dist/agents/backtest.js +92 -0
- package/dist/agents/builtins.d.ts +21 -0
- package/dist/agents/builtins.js +102 -0
- package/dist/agents/draft-spec.d.ts +44 -0
- package/dist/agents/draft-spec.js +195 -0
- package/dist/agents/edit-safety-resolve.d.ts +70 -0
- package/dist/agents/edit-safety-resolve.js +195 -0
- package/dist/agents/edit-safety-runner.d.ts +68 -0
- package/dist/agents/edit-safety-runner.js +156 -0
- package/dist/agents/edit-safety.d.ts +82 -0
- package/dist/agents/edit-safety.js +425 -0
- package/dist/agents/fix-writer.d.ts +38 -0
- package/dist/agents/fix-writer.js +197 -0
- package/dist/agents/inbox.d.ts +141 -0
- package/dist/agents/inbox.js +360 -0
- package/dist/agents/learning.d.ts +32 -0
- package/dist/agents/learning.js +71 -0
- package/dist/agents/mention.d.ts +76 -0
- package/dist/agents/mention.js +161 -0
- package/dist/agents/run-spec.d.ts +27 -0
- package/dist/agents/run-spec.js +82 -0
- package/dist/agents/schedule.d.ts +22 -0
- package/dist/agents/schedule.js +53 -0
- package/dist/agents/settings.d.ts +30 -0
- package/dist/agents/settings.js +64 -0
- package/dist/agents/spec.d.ts +84 -0
- package/dist/agents/spec.js +70 -0
- package/dist/agents/stats.d.ts +15 -0
- package/dist/agents/stats.js +48 -0
- package/dist/agents/templates.d.ts +34 -0
- package/dist/agents/templates.js +268 -0
- package/dist/agents/tick.d.ts +59 -0
- package/dist/agents/tick.js +161 -0
- package/dist/chat/anthropic-model-caps.d.ts +19 -0
- package/dist/chat/anthropic-model-caps.js +56 -0
- package/dist/chat/anthropic-planner.d.ts +1 -0
- package/dist/chat/anthropic-planner.js +72 -15
- package/dist/chat/chat-pipeline.js +54 -1
- package/dist/chat/model-defaults.js +6 -3
- package/dist/checks/rules-draft.d.ts +6 -1
- package/dist/checks/rules-draft.js +10 -7
- package/dist/checks/rules-i18n.d.ts +25 -0
- package/dist/checks/rules-i18n.js +193 -0
- package/dist/checks/run-checks.d.ts +16 -1
- package/dist/checks/run-checks.js +80 -63
- package/dist/checks/session-runner.js +7 -0
- package/dist/checks/types.d.ts +6 -0
- package/dist/durable/in-memory-durable-store.d.ts +34 -1
- package/dist/durable/in-memory-durable-store.js +122 -1
- package/dist/durable/index.d.ts +1 -1
- package/dist/durable/sqlite-durable-store.d.ts +19 -1
- package/dist/durable/sqlite-durable-store.js +220 -1
- package/dist/durable/types.d.ts +146 -0
- package/dist/handler/create-orchestrator.js +191 -1
- package/dist/http/history-actions.d.ts +22 -0
- package/dist/http/history-actions.js +33 -5
- package/dist/http/inbox-actions.d.ts +156 -0
- package/dist/http/inbox-actions.js +498 -0
- package/dist/http/ops-actions.d.ts +17 -1
- package/dist/http/ops-actions.js +36 -0
- package/dist/http/variation-preview-actions.d.ts +30 -0
- package/dist/http/variation-preview-actions.js +75 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +9 -0
- package/dist/nlp/deterministic-planner.d.ts +1 -1
- package/dist/nlp/intent-detection.d.ts +15 -0
- package/dist/nlp/intent-detection.js +5 -0
- package/dist/state/session-state.d.ts +11 -3
- package/dist/state/session-state.js +8 -0
- package/dist/state/sqlite-store.d.ts +12 -0
- package/dist/telemetry/usage.js +4 -0
- package/package.json +3 -3
|
@@ -16,16 +16,19 @@ import { DRAFT_RULES } from "./rules-draft.js";
|
|
|
16
16
|
export function fingerprintFor(scopeKey, slug, ruleId, key = "") {
|
|
17
17
|
return createHash("sha256").update([scopeKey, slug, ruleId, key].join("\u0000")).digest("hex").slice(0, 32);
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The rules over a site, with no store and no run ledger — pure in-memory work
|
|
21
|
+
* over the pages given. `runDraftChecks` records what this returns; the first
|
|
22
|
+
* run's backtest and the re-check before a fix is applied use it directly,
|
|
23
|
+
* because neither may write a finding.
|
|
24
|
+
*/
|
|
25
|
+
export function evaluateDraftChecks(args) {
|
|
22
26
|
const rules = args.rules ?? DRAFT_RULES;
|
|
23
|
-
const runId = args.runId ?? randomUUID();
|
|
24
|
-
const startedAt = now();
|
|
25
27
|
const site = {
|
|
26
28
|
slugs: args.pages.map((p) => p.slug),
|
|
27
29
|
pages: args.pages.map((p) => ({ slug: p.slug, title: p.title, ...(p.meta ? { meta: p.meta } : {}) })),
|
|
28
30
|
config: args.siteConfig ?? {},
|
|
31
|
+
page: (slug) => args.pages.find((p) => p.slug === slug),
|
|
29
32
|
// Present only when the caller could enumerate the site's documents.
|
|
30
33
|
// Passing `[]` where it could not would make every file link report broken.
|
|
31
34
|
...(args.assets ? { assets: args.assets } : {})
|
|
@@ -50,73 +53,85 @@ export async function runDraftChecks(args) {
|
|
|
50
53
|
linksBySlug.set(page.slug, walkPageLinks(page, args.manifest));
|
|
51
54
|
}
|
|
52
55
|
const weights = computePageWeights({ pages: site.pages, fieldsBySlug, config: site.config });
|
|
56
|
+
const findings = [];
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
let error;
|
|
59
|
+
for (const page of scanned) {
|
|
60
|
+
const pageWeight = weights.get(page.slug)?.weight ?? NEUTRAL_PAGE_WEIGHT;
|
|
61
|
+
const ctx = {
|
|
62
|
+
scopeKey: args.scopeKey,
|
|
63
|
+
page,
|
|
64
|
+
site,
|
|
65
|
+
manifest: args.manifest,
|
|
66
|
+
fields: fieldsBySlug.get(page.slug) ?? [],
|
|
67
|
+
links: linksBySlug.get(page.slug) ?? []
|
|
68
|
+
};
|
|
69
|
+
for (const rule of rules) {
|
|
70
|
+
// One rule throwing must not cost the run every other rule's findings —
|
|
71
|
+
// a checker that goes dark because a custom block had an unexpected
|
|
72
|
+
// prop shape is worse than one that reports twelve of thirteen rules.
|
|
73
|
+
let produced;
|
|
74
|
+
try {
|
|
75
|
+
produced = rule.run(ctx);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
error ??= `${rule.id}: ${err instanceof Error ? err.message : String(err)}`;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
for (const finding of produced) {
|
|
82
|
+
const fingerprint = fingerprintFor(args.scopeKey, page.slug, rule.id, finding.key);
|
|
83
|
+
/*
|
|
84
|
+
* Two findings from one rule sharing a key are one finding as far as
|
|
85
|
+
* the store is concerned — the second upsert overwrites the first.
|
|
86
|
+
* Collapsing them here instead keeps the ledger honest (the second
|
|
87
|
+
* was being counted as an `updated` row) and makes which one survives
|
|
88
|
+
* a decision rather than an accident of iteration order.
|
|
89
|
+
*/
|
|
90
|
+
if (seen.has(fingerprint))
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(fingerprint);
|
|
93
|
+
const severity = finding.severity ?? rule.severity;
|
|
94
|
+
findings.push({
|
|
95
|
+
fingerprint,
|
|
96
|
+
scopeKey: args.scopeKey,
|
|
97
|
+
slug: page.slug,
|
|
98
|
+
ruleId: rule.id,
|
|
99
|
+
agent: rule.agent,
|
|
100
|
+
severity,
|
|
101
|
+
impact: impactFor(severity, pageWeight),
|
|
102
|
+
title: finding.title,
|
|
103
|
+
...(finding.detail ? { detail: finding.detail } : {}),
|
|
104
|
+
...(finding.evidence ? { evidence: finding.evidence } : {}),
|
|
105
|
+
...(finding.proposedOps ? { proposedOps: finding.proposedOps } : {})
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { findings, scannedSlugs: scanned.map((p) => p.slug), ...(error ? { error } : {}) };
|
|
111
|
+
}
|
|
112
|
+
export async function runDraftChecks(args) {
|
|
113
|
+
const store = args.store ?? getDurableStore();
|
|
114
|
+
const now = args.now ?? Date.now;
|
|
115
|
+
const rules = args.rules ?? DRAFT_RULES;
|
|
116
|
+
const runId = args.runId ?? randomUUID();
|
|
117
|
+
const startedAt = now();
|
|
53
118
|
await store.startCheckRun({
|
|
54
119
|
id: runId,
|
|
55
120
|
scopeKey: args.scopeKey,
|
|
56
|
-
agent: rules.length === 1 ? rules[0].agent : "checks",
|
|
121
|
+
agent: args.agent ?? (rules.length === 1 ? rules[0].agent : "checks"),
|
|
57
122
|
trigger: args.trigger ?? "manual",
|
|
58
123
|
startedAt
|
|
59
124
|
});
|
|
60
|
-
const findings = [];
|
|
61
|
-
const seen = new Set();
|
|
62
125
|
let error;
|
|
63
126
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
scopeKey: args.scopeKey,
|
|
68
|
-
page,
|
|
69
|
-
site,
|
|
70
|
-
manifest: args.manifest,
|
|
71
|
-
fields: fieldsBySlug.get(page.slug) ?? [],
|
|
72
|
-
links: linksBySlug.get(page.slug) ?? []
|
|
73
|
-
};
|
|
74
|
-
for (const rule of rules) {
|
|
75
|
-
// One rule throwing must not cost the run every other rule's findings —
|
|
76
|
-
// a checker that goes dark because a custom block had an unexpected
|
|
77
|
-
// prop shape is worse than one that reports twelve of thirteen rules.
|
|
78
|
-
let produced;
|
|
79
|
-
try {
|
|
80
|
-
produced = rule.run(ctx);
|
|
81
|
-
}
|
|
82
|
-
catch (err) {
|
|
83
|
-
error ??= `${rule.id}: ${err instanceof Error ? err.message : String(err)}`;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
for (const finding of produced) {
|
|
87
|
-
const fingerprint = fingerprintFor(args.scopeKey, page.slug, rule.id, finding.key);
|
|
88
|
-
/*
|
|
89
|
-
* Two findings from one rule sharing a key are one finding as far as
|
|
90
|
-
* the store is concerned — the second upsert overwrites the first.
|
|
91
|
-
* Collapsing them here instead keeps the ledger honest (the second
|
|
92
|
-
* was being counted as an `updated` row) and makes which one survives
|
|
93
|
-
* a decision rather than an accident of iteration order.
|
|
94
|
-
*/
|
|
95
|
-
if (seen.has(fingerprint))
|
|
96
|
-
continue;
|
|
97
|
-
seen.add(fingerprint);
|
|
98
|
-
const severity = finding.severity ?? rule.severity;
|
|
99
|
-
findings.push({
|
|
100
|
-
fingerprint,
|
|
101
|
-
scopeKey: args.scopeKey,
|
|
102
|
-
slug: page.slug,
|
|
103
|
-
ruleId: rule.id,
|
|
104
|
-
agent: rule.agent,
|
|
105
|
-
severity,
|
|
106
|
-
impact: impactFor(severity, pageWeight),
|
|
107
|
-
title: finding.title,
|
|
108
|
-
...(finding.detail ? { detail: finding.detail } : {}),
|
|
109
|
-
...(finding.evidence ? { evidence: finding.evidence } : {}),
|
|
110
|
-
...(finding.proposedOps ? { proposedOps: finding.proposedOps } : {})
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
127
|
+
const evaluated = evaluateDraftChecks(args);
|
|
128
|
+
const findings = evaluated.findings;
|
|
129
|
+
error = evaluated.error;
|
|
115
130
|
const { opened } = await store.recordFindings(runId, findings);
|
|
116
131
|
// Reconcile per agent, not once for the whole run. A run of only the SEO
|
|
117
132
|
// rules that closed everything in scope would mark this morning's
|
|
118
133
|
// accessibility findings fixed without having looked at them.
|
|
119
|
-
const scannedSlugs =
|
|
134
|
+
const scannedSlugs = evaluated.scannedSlugs;
|
|
120
135
|
let closed = 0;
|
|
121
136
|
for (const agent of new Set(rules.map((r) => r.agent))) {
|
|
122
137
|
const result = await store.reconcileFindings({
|
|
@@ -131,11 +146,11 @@ export async function runDraftChecks(args) {
|
|
|
131
146
|
const record = {
|
|
132
147
|
id: runId,
|
|
133
148
|
scopeKey: args.scopeKey,
|
|
134
|
-
agent: rules.length === 1 ? rules[0].agent : "checks",
|
|
149
|
+
agent: args.agent ?? (rules.length === 1 ? rules[0].agent : "checks"),
|
|
135
150
|
trigger: args.trigger ?? "manual",
|
|
136
151
|
startedAt,
|
|
137
152
|
finishedAt: now(),
|
|
138
|
-
pagesScanned:
|
|
153
|
+
pagesScanned: scannedSlugs.length,
|
|
139
154
|
findingsOpened: opened,
|
|
140
155
|
findingsClosed: closed,
|
|
141
156
|
costUsd: 0,
|
|
@@ -153,7 +168,9 @@ export async function runDraftChecks(args) {
|
|
|
153
168
|
}
|
|
154
169
|
catch (err) {
|
|
155
170
|
const reason = err instanceof Error ? err.message : String(err);
|
|
156
|
-
|
|
171
|
+
const wanted = args.slugs ? new Set(args.slugs) : null;
|
|
172
|
+
const pagesScanned = args.pages.filter((p) => (wanted ? wanted.has(p.slug) : true)).length;
|
|
173
|
+
await store.finishCheckRun(runId, { finishedAt: now(), pagesScanned, error: reason });
|
|
157
174
|
throw err;
|
|
158
175
|
}
|
|
159
176
|
}
|
|
@@ -54,6 +54,13 @@ function enabled(flag) {
|
|
|
54
54
|
return process.env.CHECKS_ON_PUBLISH !== "0";
|
|
55
55
|
}
|
|
56
56
|
function runInBackground(scopeKey, trigger, log) {
|
|
57
|
+
// Custom agents switched on for this event run after the draft checker,
|
|
58
|
+
// on the same engine (agents/run-spec.ts). Imported lazily: the agents
|
|
59
|
+
// module reads settings from the durable store, which the checker alone
|
|
60
|
+
// never needs.
|
|
61
|
+
void import("../agents/run-spec.js")
|
|
62
|
+
.then(({ runCustomAgentsOn }) => runCustomAgentsOn({ scopeKey, on: trigger === "on_apply" ? "apply" : "publish", ...(log ? { log } : {}) }))
|
|
63
|
+
.catch((err) => log?.error({ err: String(err), scopeKey, trigger }, "agent runs failed"));
|
|
57
64
|
void runChecksForSession({ scopeKey, trigger })
|
|
58
65
|
.then((run) => {
|
|
59
66
|
log?.info({ scopeKey, trigger, opened: run.findingsOpened, closed: run.findingsClosed }, "checks run complete");
|
package/dist/checks/types.d.ts
CHANGED
|
@@ -60,6 +60,12 @@ export type SiteView = {
|
|
|
60
60
|
meta?: PageDoc["meta"];
|
|
61
61
|
}>;
|
|
62
62
|
config: SiteConfig;
|
|
63
|
+
/**
|
|
64
|
+
* Another page in full, by slug. Most rules need only titles; the
|
|
65
|
+
* translation rules compare a page with its source-language counterpart
|
|
66
|
+
* block by block. Absent where a caller built a SiteView by hand.
|
|
67
|
+
*/
|
|
68
|
+
page?: (slug: string) => PageDoc | undefined;
|
|
63
69
|
/**
|
|
64
70
|
* The documents the site holds, when it can enumerate them.
|
|
65
71
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CheckRunInput, CheckRunPatch, CorrectionInput, CorrectionQuery, DurableStore, FindingInput, FindingQuery, FindingSeverity, FindingStatus, MemoryInput, MemoryQuery, MemoryStatus, ProposalInput, ProposalQuery, ProposalStatus } from "./types.ts";
|
|
1
|
+
import type { CheckRunInput, CheckRunPatch, CorrectionInput, CorrectionQuery, DurableStore, FindingFix, FindingInput, FindingQuery, FindingSeverity, FindingStatus, MemoryInput, MemoryQuery, MemoryStatus, ProposalInput, ProposalQuery, ProposalStatus, InboxItemInput, InboxItemQuery, InboxItemRecord, InboxItemResolution, AgentSettingInput } from "./types.ts";
|
|
2
2
|
export type InMemoryDurableStoreOptions = {
|
|
3
3
|
now?: () => number;
|
|
4
4
|
};
|
|
@@ -10,6 +10,8 @@ export declare class InMemoryDurableStore implements DurableStore {
|
|
|
10
10
|
private readonly memory;
|
|
11
11
|
private readonly corrections;
|
|
12
12
|
private readonly proposals;
|
|
13
|
+
private readonly inbox;
|
|
14
|
+
private readonly agents;
|
|
13
15
|
private readonly now;
|
|
14
16
|
constructor(options?: InMemoryDurableStoreOptions);
|
|
15
17
|
/** Keep the newest `cap` rows per scope, matching the SQLite store's trim. */
|
|
@@ -40,6 +42,7 @@ export declare class InMemoryDurableStore implements DurableStore {
|
|
|
40
42
|
evidence?: import("./types.ts").FindingEvidence;
|
|
41
43
|
proposedOps?: import("@avocadostudio-ai/shared").Operation[];
|
|
42
44
|
id: string;
|
|
45
|
+
fix?: FindingFix;
|
|
43
46
|
status: FindingStatus;
|
|
44
47
|
lastRunId: string;
|
|
45
48
|
firstSeenAt: number;
|
|
@@ -60,6 +63,7 @@ export declare class InMemoryDurableStore implements DurableStore {
|
|
|
60
63
|
evidence?: import("./types.ts").FindingEvidence;
|
|
61
64
|
proposedOps?: import("@avocadostudio-ai/shared").Operation[];
|
|
62
65
|
id: string;
|
|
66
|
+
fix?: FindingFix;
|
|
63
67
|
status: FindingStatus;
|
|
64
68
|
lastRunId: string;
|
|
65
69
|
firstSeenAt: number;
|
|
@@ -67,6 +71,7 @@ export declare class InMemoryDurableStore implements DurableStore {
|
|
|
67
71
|
resolvedAt?: number;
|
|
68
72
|
snoozedUntil?: number;
|
|
69
73
|
} | null>;
|
|
74
|
+
setFindingFix(id: string, fix: FindingFix | null): Promise<void>;
|
|
70
75
|
setFindingStatus(id: string, status: FindingStatus, at?: number): Promise<void>;
|
|
71
76
|
wakeSnoozedFindings(scopeKey: string, now?: number): Promise<{
|
|
72
77
|
woken: number;
|
|
@@ -200,4 +205,32 @@ export declare class InMemoryDurableStore implements DurableStore {
|
|
|
200
205
|
expireProposals(now?: number): Promise<{
|
|
201
206
|
expired: number;
|
|
202
207
|
}>;
|
|
208
|
+
putInboxItem(input: InboxItemInput): Promise<InboxItemRecord>;
|
|
209
|
+
getInboxItem(id: string): Promise<InboxItemRecord | null>;
|
|
210
|
+
listInboxItems(query: InboxItemQuery): Promise<InboxItemRecord[]>;
|
|
211
|
+
resolveInboxItem(id: string, resolution: InboxItemResolution): Promise<InboxItemRecord | null>;
|
|
212
|
+
reopenInboxItem(id: string, change?: {
|
|
213
|
+
payload?: Record<string, unknown>;
|
|
214
|
+
at?: number;
|
|
215
|
+
}): Promise<InboxItemRecord | null>;
|
|
216
|
+
putAgentSetting(input: AgentSettingInput, at?: number): Promise<{
|
|
217
|
+
scopeKey: string;
|
|
218
|
+
agentId: string;
|
|
219
|
+
enabled: boolean;
|
|
220
|
+
owner?: string;
|
|
221
|
+
spec?: Record<string, unknown>;
|
|
222
|
+
createdAt: number;
|
|
223
|
+
updatedAt: number;
|
|
224
|
+
}>;
|
|
225
|
+
deleteAgentSetting(scopeKey: string, agentId: string): Promise<boolean>;
|
|
226
|
+
listAgentScopes(): Promise<string[]>;
|
|
227
|
+
listAgentSettings(scopeKey: string): Promise<{
|
|
228
|
+
scopeKey: string;
|
|
229
|
+
agentId: string;
|
|
230
|
+
enabled: boolean;
|
|
231
|
+
owner?: string;
|
|
232
|
+
spec?: Record<string, unknown>;
|
|
233
|
+
createdAt: number;
|
|
234
|
+
updatedAt: number;
|
|
235
|
+
}[]>;
|
|
203
236
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { CHECK_RUN_CAP, CORRECTION_CAP, PROPOSAL_CAP, SNOOZE_WINDOW_MS } from "./sqlite-durable-store.js";
|
|
2
|
+
import { CHECK_RUN_CAP, CORRECTION_CAP, INBOX_RESOLVED_CAP, PROPOSAL_CAP, SNOOZE_WINDOW_MS } from "./sqlite-durable-store.js";
|
|
3
3
|
import { fallbackImpact } from "./finding-impact.js";
|
|
4
4
|
/*
|
|
5
5
|
* In-memory `DurableStore`. Two jobs, both real.
|
|
@@ -28,6 +28,8 @@ export class InMemoryDurableStore {
|
|
|
28
28
|
memory = new Map();
|
|
29
29
|
corrections = [];
|
|
30
30
|
proposals = new Map();
|
|
31
|
+
inbox = new Map();
|
|
32
|
+
agents = new Map();
|
|
31
33
|
now;
|
|
32
34
|
constructor(options = {}) {
|
|
33
35
|
this.now = options.now ?? Date.now;
|
|
@@ -154,6 +156,17 @@ export class InMemoryDurableStore {
|
|
|
154
156
|
const record = this.findings.get(id);
|
|
155
157
|
return record ? { ...record } : null;
|
|
156
158
|
}
|
|
159
|
+
async setFindingFix(id, fix) {
|
|
160
|
+
const record = this.findings.get(id);
|
|
161
|
+
if (!record)
|
|
162
|
+
return;
|
|
163
|
+
const next = { ...record };
|
|
164
|
+
if (fix)
|
|
165
|
+
next.fix = structuredClone(fix);
|
|
166
|
+
else
|
|
167
|
+
delete next.fix;
|
|
168
|
+
this.findings.set(id, next);
|
|
169
|
+
}
|
|
157
170
|
async setFindingStatus(id, status, at) {
|
|
158
171
|
const record = this.findings.get(id);
|
|
159
172
|
if (!record)
|
|
@@ -360,4 +373,112 @@ export class InMemoryDurableStore {
|
|
|
360
373
|
}
|
|
361
374
|
return { expired };
|
|
362
375
|
}
|
|
376
|
+
// Inbox items ------------------------------------------------------------
|
|
377
|
+
async putInboxItem(input) {
|
|
378
|
+
const at = this.now();
|
|
379
|
+
const existing = this.inbox.get(input.id);
|
|
380
|
+
const record = existing
|
|
381
|
+
? {
|
|
382
|
+
...input,
|
|
383
|
+
createdAt: existing.createdAt,
|
|
384
|
+
scopeKey: existing.scopeKey,
|
|
385
|
+
status: existing.status,
|
|
386
|
+
revision: existing.revision + 1,
|
|
387
|
+
updatedAt: at,
|
|
388
|
+
...(existing.resolvedAt != null ? { resolvedAt: existing.resolvedAt } : {}),
|
|
389
|
+
...(existing.resolution != null ? { resolution: existing.resolution } : {}),
|
|
390
|
+
...(existing.resolvedBy != null ? { resolvedBy: existing.resolvedBy } : {}),
|
|
391
|
+
...(existing.note != null ? { note: existing.note } : {})
|
|
392
|
+
}
|
|
393
|
+
: { ...input, status: "open", revision: 1, updatedAt: at };
|
|
394
|
+
this.inbox.set(input.id, record);
|
|
395
|
+
const closed = [...this.inbox.values()]
|
|
396
|
+
.filter((row) => row.scopeKey === record.scopeKey && row.status !== "open")
|
|
397
|
+
.sort((a, b) => b.createdAt - a.createdAt);
|
|
398
|
+
closed.slice(INBOX_RESOLVED_CAP).forEach((row) => this.inbox.delete(row.id));
|
|
399
|
+
return structuredClone(record);
|
|
400
|
+
}
|
|
401
|
+
async getInboxItem(id) {
|
|
402
|
+
const record = this.inbox.get(id);
|
|
403
|
+
return record ? structuredClone(record) : null;
|
|
404
|
+
}
|
|
405
|
+
async listInboxItems(query) {
|
|
406
|
+
const statuses = query.status ? (Array.isArray(query.status) ? query.status : [query.status]) : null;
|
|
407
|
+
const limit = Math.max(1, Math.min(query.limit ?? 200, 1000));
|
|
408
|
+
return [...this.inbox.values()]
|
|
409
|
+
.filter((row) => row.scopeKey === query.scopeKey)
|
|
410
|
+
.filter((row) => (statuses ? statuses.includes(row.status) : true))
|
|
411
|
+
.filter((row) => (query.kind ? row.kind === query.kind : true))
|
|
412
|
+
.filter((row) => (query.agent ? row.agent === query.agent : true))
|
|
413
|
+
.filter((row) => (query.updatedSince != null ? row.updatedAt > query.updatedSince : true))
|
|
414
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
415
|
+
.slice(0, limit)
|
|
416
|
+
.map((row) => structuredClone(row));
|
|
417
|
+
}
|
|
418
|
+
async resolveInboxItem(id, resolution) {
|
|
419
|
+
const current = this.inbox.get(id);
|
|
420
|
+
if (!current || current.status !== "open")
|
|
421
|
+
return null;
|
|
422
|
+
const at = resolution.at ?? this.now();
|
|
423
|
+
const payload = resolution.payload ? { ...(current.payload ?? {}), ...resolution.payload } : current.payload;
|
|
424
|
+
const next = {
|
|
425
|
+
...current,
|
|
426
|
+
status: "resolved",
|
|
427
|
+
resolvedAt: at,
|
|
428
|
+
resolution: resolution.resolution,
|
|
429
|
+
revision: current.revision + 1,
|
|
430
|
+
updatedAt: at,
|
|
431
|
+
...(payload ? { payload } : {}),
|
|
432
|
+
...(resolution.resolvedBy != null ? { resolvedBy: resolution.resolvedBy } : {}),
|
|
433
|
+
...(resolution.note != null ? { note: resolution.note } : {})
|
|
434
|
+
};
|
|
435
|
+
this.inbox.set(id, next);
|
|
436
|
+
return structuredClone(next);
|
|
437
|
+
}
|
|
438
|
+
async reopenInboxItem(id, change = {}) {
|
|
439
|
+
const current = this.inbox.get(id);
|
|
440
|
+
if (!current || current.status !== "resolved")
|
|
441
|
+
return null;
|
|
442
|
+
const at = change.at ?? this.now();
|
|
443
|
+
const payload = change.payload ? { ...(current.payload ?? {}), ...change.payload } : current.payload;
|
|
444
|
+
const { resolvedAt: _r, resolution: _s, resolvedBy: _b, note: _n, ...rest } = current;
|
|
445
|
+
const next = {
|
|
446
|
+
...rest,
|
|
447
|
+
status: "open",
|
|
448
|
+
revision: current.revision + 1,
|
|
449
|
+
updatedAt: at,
|
|
450
|
+
...(payload ? { payload } : {})
|
|
451
|
+
};
|
|
452
|
+
this.inbox.set(id, next);
|
|
453
|
+
return structuredClone(next);
|
|
454
|
+
}
|
|
455
|
+
// Agent settings ---------------------------------------------------------
|
|
456
|
+
async putAgentSetting(input, at) {
|
|
457
|
+
const now = at ?? this.now();
|
|
458
|
+
const key = `${input.scopeKey}\0${input.agentId}`;
|
|
459
|
+
const existing = this.agents.get(key);
|
|
460
|
+
const record = {
|
|
461
|
+
scopeKey: input.scopeKey,
|
|
462
|
+
agentId: input.agentId,
|
|
463
|
+
enabled: input.enabled,
|
|
464
|
+
...(input.owner != null ? { owner: input.owner } : {}),
|
|
465
|
+
...(input.spec ? { spec: structuredClone(input.spec) } : existing?.spec ? { spec: existing.spec } : {}),
|
|
466
|
+
createdAt: existing?.createdAt ?? now,
|
|
467
|
+
updatedAt: now
|
|
468
|
+
};
|
|
469
|
+
this.agents.set(key, record);
|
|
470
|
+
return { ...record };
|
|
471
|
+
}
|
|
472
|
+
async deleteAgentSetting(scopeKey, agentId) {
|
|
473
|
+
return this.agents.delete(`${scopeKey}\0${agentId}`);
|
|
474
|
+
}
|
|
475
|
+
async listAgentScopes() {
|
|
476
|
+
return [...new Set([...this.agents.values()].map((row) => row.scopeKey))].sort();
|
|
477
|
+
}
|
|
478
|
+
async listAgentSettings(scopeKey) {
|
|
479
|
+
return [...this.agents.values()]
|
|
480
|
+
.filter((row) => row.scopeKey === scopeKey)
|
|
481
|
+
.sort((a, b) => a.agentId.localeCompare(b.agentId))
|
|
482
|
+
.map((row) => ({ ...row }));
|
|
483
|
+
}
|
|
363
484
|
}
|
package/dist/durable/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export { getDurableStore, resetDurableStore, setDurableStore, durableStoreIsEphe
|
|
|
2
2
|
export { loadPendingPlan, savePendingPlan, clearPendingPlan, peekPendingPlan } from "./pending-plan-store.ts";
|
|
3
3
|
export { SqliteDurableStore, type SqliteDurableStoreOptions } from "./sqlite-durable-store.ts";
|
|
4
4
|
export { InMemoryDurableStore, type InMemoryDurableStoreOptions } from "./in-memory-durable-store.ts";
|
|
5
|
-
export type { DurableStore, FindingInput, FindingRecord, FindingQuery, FindingSeverity, FindingStatus, FindingEvidence, CheckRunInput, CheckRunRecord, CheckRunPatch, CheckRunTrigger, MemoryInput, MemoryRecord, MemoryQuery, MemoryScope, MemoryKind, MemorySource, MemoryStatus, CorrectionInput, CorrectionRecord, CorrectionQuery, CorrectionOutcome, ProposalInput, ProposalRecord, ProposalQuery, ProposalStatus } from "./types.ts";
|
|
5
|
+
export type { DurableStore, FindingInput, FindingRecord, FindingQuery, FindingSeverity, FindingStatus, FindingEvidence, CheckRunInput, CheckRunRecord, CheckRunPatch, CheckRunTrigger, MemoryInput, MemoryRecord, MemoryQuery, MemoryScope, MemoryKind, MemorySource, MemoryStatus, CorrectionInput, CorrectionRecord, CorrectionQuery, CorrectionOutcome, ProposalInput, ProposalRecord, ProposalQuery, ProposalStatus, InboxItemKind, InboxItemStatus, InboxItemInput, InboxItemRecord, InboxItemQuery, InboxItemResolution, AgentSettingInput, AgentSettingRecord } from "./types.ts";
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { Database as BetterSqliteDatabase } from "better-sqlite3";
|
|
2
|
-
import type { CheckRunInput, CheckRunPatch, CheckRunRecord, CorrectionInput, CorrectionQuery, CorrectionRecord, DurableStore, FindingInput, FindingQuery, FindingRecord, FindingStatus, MemoryInput, MemoryQuery, MemoryRecord, MemoryStatus, ProposalInput, ProposalQuery, ProposalRecord, ProposalStatus } from "./types.ts";
|
|
2
|
+
import type { CheckRunInput, CheckRunPatch, CheckRunRecord, CorrectionInput, CorrectionQuery, CorrectionRecord, DurableStore, FindingFix, FindingInput, FindingQuery, FindingRecord, FindingStatus, MemoryInput, MemoryQuery, MemoryRecord, MemoryStatus, ProposalInput, ProposalQuery, ProposalRecord, ProposalStatus, InboxItemInput, InboxItemQuery, InboxItemRecord, InboxItemResolution, AgentSettingInput, AgentSettingRecord } from "./types.ts";
|
|
3
3
|
export declare const PROPOSAL_CAP = 200;
|
|
4
4
|
export declare const CORRECTION_CAP = 500;
|
|
5
5
|
export declare const CHECK_RUN_CAP = 200;
|
|
6
|
+
/**
|
|
7
|
+
* Only closed items count against it: an open item is somebody's decision still
|
|
8
|
+
* to make, and trimming it would silently lift a publish hold.
|
|
9
|
+
*/
|
|
10
|
+
export declare const INBOX_RESOLVED_CAP = 500;
|
|
6
11
|
/**
|
|
7
12
|
* How long a snooze lasts.
|
|
8
13
|
*
|
|
@@ -38,6 +43,7 @@ export declare class SqliteDurableStore implements DurableStore {
|
|
|
38
43
|
}>;
|
|
39
44
|
listFindings(query: FindingQuery): Promise<FindingRecord[]>;
|
|
40
45
|
getFinding(id: string): Promise<FindingRecord | null>;
|
|
46
|
+
setFindingFix(id: string, fix: FindingFix | null): Promise<void>;
|
|
41
47
|
setFindingStatus(id: string, status: FindingStatus, at?: number): Promise<void>;
|
|
42
48
|
wakeSnoozedFindings(scopeKey: string, now?: number): Promise<{
|
|
43
49
|
woken: number;
|
|
@@ -68,4 +74,16 @@ export declare class SqliteDurableStore implements DurableStore {
|
|
|
68
74
|
expireProposals(now?: number): Promise<{
|
|
69
75
|
expired: number;
|
|
70
76
|
}>;
|
|
77
|
+
putInboxItem(input: InboxItemInput): Promise<InboxItemRecord>;
|
|
78
|
+
getInboxItem(id: string): Promise<InboxItemRecord | null>;
|
|
79
|
+
listInboxItems(query: InboxItemQuery): Promise<InboxItemRecord[]>;
|
|
80
|
+
resolveInboxItem(id: string, resolution: InboxItemResolution): Promise<InboxItemRecord | null>;
|
|
81
|
+
reopenInboxItem(id: string, change?: {
|
|
82
|
+
payload?: Record<string, unknown>;
|
|
83
|
+
at?: number;
|
|
84
|
+
}): Promise<InboxItemRecord | null>;
|
|
85
|
+
putAgentSetting(input: AgentSettingInput, at?: number): Promise<AgentSettingRecord>;
|
|
86
|
+
deleteAgentSetting(scopeKey: string, agentId: string): Promise<boolean>;
|
|
87
|
+
listAgentScopes(): Promise<string[]>;
|
|
88
|
+
listAgentSettings(scopeKey: string): Promise<AgentSettingRecord[]>;
|
|
71
89
|
}
|