CI & Build / Python lint (push) Successful in 9s
CI & Build / Plugin hooks (push) Successful in 22s
CI & Build / TypeScript typecheck (push) Successful in 1m0s
CI & Build / integration (push) Successful in 1m55s
CI & Build / Python tests (push) Successful in 2m20s
CI & Build / Build & push image (push) Successful in 1m7s
Components, stores, api clients, router, types and shared CSS now state what each piece is and why, without the milestone that introduced it. diff.ts names its consumers instead of the copy history; stores/notes and tasks state their current scope instead of the cleanup that set it; theme.css states the token practice instead of the sweep that established it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
173 lines
5.0 KiB
TypeScript
173 lines
5.0 KiB
TypeScript
import { apiGet, apiPost, apiDelete } from "@/api/client";
|
|
|
|
/**
|
|
* The moments of work rules mount on, and the actions that
|
|
* reach each one on this install — `GET /api/retrieval/moments`, the payload
|
|
* the `list_moments` MCP tool returns, plus the counts the Settings view shows
|
|
* beside each moment.
|
|
*/
|
|
export interface Moment {
|
|
name: string;
|
|
/** What is happening at this moment. */
|
|
means: string;
|
|
/** The kinds of action that typically reach it. */
|
|
reached_by: string;
|
|
}
|
|
|
|
/** A shape rather than an entry: `skill.<name>`, one moment per procedure. */
|
|
export interface MomentFamily {
|
|
name: string;
|
|
prefix: string;
|
|
means: string;
|
|
reached_by: string;
|
|
}
|
|
|
|
/** `default` ships with the product; `install` is this install's own. */
|
|
export type ActionVia = "default" | "install";
|
|
|
|
export interface MomentAction {
|
|
tool: string;
|
|
/** Empty = every call of the tool. A command prefix for a command tool,
|
|
* `field=value` pairs for any other. */
|
|
match: string;
|
|
via: ActionVia;
|
|
}
|
|
|
|
/** A shipped default this install switched off. */
|
|
export interface RemovedDefault {
|
|
id: number;
|
|
tool: string;
|
|
match: string;
|
|
moment: string;
|
|
reason: string | null;
|
|
actor: string | null;
|
|
created_at: string | null;
|
|
}
|
|
|
|
export interface MomentUsage {
|
|
/** How many times a mounted rule arrived at this moment. */
|
|
delivered: number;
|
|
/** How many distinct rules did. */
|
|
rules: number;
|
|
/** Of those, how many an agent then opened — an upper bound: a pull records
|
|
* the door, not the line that prompted it. */
|
|
opened: number;
|
|
last_delivered_at: string | null;
|
|
}
|
|
|
|
export interface MomentsPayload {
|
|
moments: Moment[];
|
|
families: MomentFamily[];
|
|
total: number;
|
|
actions: Record<string, MomentAction[]>;
|
|
removed_defaults: RemovedDefault[];
|
|
/** Rules mounted per moment; a moment carrying none is absent. */
|
|
mounted: Record<string, number>;
|
|
/** The count read failed — `mounted` is empty for that reason, not because
|
|
* nothing is mounted. */
|
|
mounted_failed?: boolean;
|
|
usage: {
|
|
by_moment: Record<string, MomentUsage>;
|
|
days: number;
|
|
/** Same distinction as `mounted_failed`, for the usage read. */
|
|
moment_usage_failed?: boolean;
|
|
};
|
|
}
|
|
|
|
export interface MappingChange {
|
|
tool: string;
|
|
match?: string;
|
|
moment: string;
|
|
reason?: string;
|
|
}
|
|
|
|
export function getMoments(days = 30): Promise<MomentsPayload> {
|
|
return apiGet(`/api/retrieval/moments?days=${days}`);
|
|
}
|
|
|
|
export function mapAction(change: MappingChange): Promise<unknown> {
|
|
return apiPost("/api/retrieval/moments/mappings", change);
|
|
}
|
|
|
|
/** Query parameters, not a body: DELETE bodies are not reliably sent. */
|
|
export function unmapAction(change: MappingChange): Promise<void> {
|
|
const q = new URLSearchParams({
|
|
tool: change.tool,
|
|
match: change.match ?? "",
|
|
moment: change.moment,
|
|
reason: change.reason ?? "",
|
|
});
|
|
return apiDelete(`/api/retrieval/moments/mappings?${q.toString()}`);
|
|
}
|
|
|
|
/**
|
|
* Proposals about a rule's moments, waiting on a person. A `mount` proposal
|
|
* comes from a pass that read the rule, or from the rule being opened just
|
|
* after the moment fired. An `unmount` proposal comes from sessions reporting that the mount arrived where it did not
|
|
* apply. `GET /api/retrieval/moments/proposals`, the payload the
|
|
* `rule_moment_proposals` MCP tool returns.
|
|
*/
|
|
export type ProposalSource = "pass" | "signal" | "edit" | "misfire";
|
|
|
|
export interface MisfireReason {
|
|
why: string;
|
|
reached_by: string;
|
|
at: string;
|
|
}
|
|
|
|
export interface MomentProposal {
|
|
proposal: "mount" | "unmount";
|
|
moment: string;
|
|
source: ProposalSource;
|
|
why: string;
|
|
/** For a misfire, `situations` counts distinct days and `co_surfaced` the reports. */
|
|
evidence: { situations: number; projects: number; co_surfaced: number };
|
|
created_at: string | null;
|
|
/** Unmount proposals only: the newest reasons given, and how often each action reached the moment. */
|
|
reasons?: MisfireReason[];
|
|
reached_by?: Record<string, number>;
|
|
}
|
|
|
|
export interface RuleProposals {
|
|
id: number;
|
|
title: string;
|
|
kind: string;
|
|
statement: string;
|
|
when_to_apply: string;
|
|
home: "global" | "project";
|
|
/** What the rule is mounted on now. */
|
|
mounted: string[];
|
|
proposals: MomentProposal[];
|
|
}
|
|
|
|
export interface ProposalsPayload {
|
|
rules: RuleProposals[];
|
|
total: number;
|
|
}
|
|
|
|
export type Verdict = "confirm" | "reject";
|
|
|
|
export interface MomentJudgment {
|
|
rule_id: number;
|
|
moment: string;
|
|
verdict: Verdict;
|
|
note?: string;
|
|
}
|
|
|
|
export interface JudgeResult {
|
|
judged: { rule_id: number; moment: string; state: string }[];
|
|
refused: { rule_id: number; moment?: string; error: string }[];
|
|
}
|
|
|
|
export function getProposals(ruleId?: number): Promise<ProposalsPayload> {
|
|
return apiGet(`/api/retrieval/moments/proposals${ruleId ? `?rule_id=${ruleId}` : ""}`);
|
|
}
|
|
|
|
/**
|
|
* A confirm MOUNTS the rule on the moment (on an unmount proposal, keeps it);
|
|
* a reject unmounts it if mounted and stops the pair being proposed again.
|
|
*/
|
|
export function judgeProposals(judgments: MomentJudgment[]): Promise<JudgeResult> {
|
|
return apiPost("/api/retrieval/moments/proposals/judge", { judgments });
|
|
}
|