@miadi/connector 0.1.20 → 0.1.22

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.
@@ -1,4 +1,4 @@
1
- import { steerAttempts, steeringGrantOf, checkGrant } from "./steer.js";
1
+ import { steerAttempts, steeringGrantOf, steeringRevoked, checkGrant } from "./steer.js";
2
2
  import { missionResult, redactResult, submittedWithWriteTier } from "./mission-result.js";
3
3
  const ISSUE_REF = /^([\w.-]+)\/([\w.-]+)#(\d+)$/;
4
4
  const SESSION_REF = /^session:([0-9a-f-]{36})$/;
@@ -389,8 +389,14 @@ export async function missionStatus(ref, records, deps) {
389
389
  }
390
390
  const grant = steeringGrantOf(record);
391
391
  const attempts = steerAttempts(records, record.id);
392
- const grantCheck = grant ? checkGrant(grant, attempts, now) : null;
393
- const grantState = grantCheck === null ? "none" : grantCheck === "ok" ? "active" : grantCheck === "expired_grant" ? "expired_grant" : "exhausted";
392
+ const grantCheck = grant ? checkGrant(grant, attempts, now, steeringRevoked(records, record.id)) : null;
393
+ const grantState = grantCheck === null ? "none" : grantCheck === "ok" ? "active" : grantCheck === "expired_grant" ? "expired_grant" : grantCheck === "revoked" ? "revoked" : "exhausted";
394
+ const steers = deps.steerDeliveryOf
395
+ ? records.filter((r) => r.kind === "session_steer_request" && r.refs.includes(record.id)).flatMap((r) => {
396
+ const delivery = deps.steerDeliveryOf(r);
397
+ return delivery ? [{ id: r.id, at: r.created_at, by: r.actor.name, state: delivery.state, route: delivery.route, delivery }] : [];
398
+ })
399
+ : [];
394
400
  const receipt = (record.completion_receipt ?? {});
395
401
  const anyLive = sessions.some((s) => s.live === "running");
396
402
  const prs = issues.filter((i) => i.merged !== undefined);
@@ -464,7 +470,7 @@ export async function missionStatus(ref, records, deps) {
464
470
  ...(queue ? { queue } : {}),
465
471
  sessions,
466
472
  issues,
467
- steering: { grant: grantState, attempts_used: attempts, ...(grant?.max_attempts !== undefined ? { max_attempts: grant.max_attempts } : {}) },
473
+ steering: { grant: grantState, attempts_used: attempts, ...(grant?.max_attempts !== undefined ? { max_attempts: grant.max_attempts } : {}), ...(steers.length ? { steers } : {}) },
468
474
  ...(provider ? { provider } : {}),
469
475
  stage_truth: stageTruth,
470
476
  mission_stage: missionStage(standing ?? null, stageTruth),
@@ -113,7 +113,7 @@ export interface JevRequest {
113
113
  state: Record<string, unknown>;
114
114
  model: string;
115
115
  questions: Record<string, {
116
- type: "choice";
116
+ type: "choice" | "noul";
117
117
  instructions: string;
118
118
  criteria: Record<string, string>;
119
119
  }>;
package/dist/records.d.ts CHANGED
@@ -36,7 +36,7 @@ export declare const DOMAINS: readonly ["creative", "ceremonial", "engineering"]
36
36
  /** #815 "claim category": what kind of statement a record makes. */
37
37
  export declare const CLAIM_CATEGORIES: readonly ["observed", "hypothesis", "desired_future", "fiction", "verified_implementation"];
38
38
  export type ClaimCategory = (typeof CLAIM_CATEGORIES)[number];
39
- export declare const RECORD_KINDS: readonly ["observation", "inquiry", "link", "work_proposal", "run_request", "handoff", "source_intent", "review_operation_request", "deep_diver_request", "review_episode_link_request", "calendar_event_draft", "session_completion_proposal", "research_artifact", "inquiry_episode_request", "session_steer_request", "provider_choice", "provider_policy"];
39
+ export declare const RECORD_KINDS: readonly ["observation", "inquiry", "link", "work_proposal", "run_request", "handoff", "source_intent", "review_operation_request", "deep_diver_request", "review_episode_link_request", "calendar_event_draft", "session_completion_proposal", "research_artifact", "inquiry_episode_request", "session_steer_request", "steering_revocation", "provider_choice", "provider_policy"];
40
40
  export type RecordKind = (typeof RECORD_KINDS)[number];
41
41
  /** #816 handoff states. */
42
42
  export declare const HANDOFF_STATES: readonly ["pending", "blocked", "ready", "in_progress", "completed", "withdrawn"];
package/dist/records.js CHANGED
@@ -61,6 +61,8 @@ export const RECORD_KINDS = [
61
61
  "research_artifact",
62
62
  "inquiry_episode_request",
63
63
  "session_steer_request",
64
+ // A person stops a run's steering, or one steer still waiting at the runtime (jgwill/Miadi#930).
65
+ "steering_revocation",
64
66
  // A requester's own per-mission provider choice and provider policy, made on the connector's pages (jgwill/Miadi#926).
65
67
  "provider_choice",
66
68
  "provider_policy",
@@ -29,7 +29,12 @@ const OUTCOME_WORDS = {
29
29
  marked_ready: "marked ready for Deep Diver",
30
30
  decided: "recorded",
31
31
  nothing_to_run: "nothing to run",
32
- accepted: "accepted by the session",
32
+ accepted: "typed into an idle session, which turned busy; its delivery says whether the model read it",
33
+ queued_at_runtime: "handed to the session's inbox, on its way to the model",
34
+ delivered_to_agent_context: "delivered: the model has it",
35
+ acknowledged: "delivered, and the agent said so",
36
+ behavior_adjusted: "delivered, and the agent's next action shows it",
37
+ expired: "expired before the model read it",
33
38
  submitted: "typed into the session, acceptance unconfirmed",
34
39
  failed: "failed",
35
40
  };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * What a shelf report is, read from its own Markdown, for a shelf a person can look through: its
3
+ * title, its opening, how long it is, what is inside, how many sources it cites. Everything here is
4
+ * computed from the file; nothing is summarised or invented. jgwill/Miadi#879.
5
+ */
6
+ export interface ReportDigest {
7
+ title: string;
8
+ /** The document names itself; otherwise the title is its folder's name. */
9
+ titled: boolean;
10
+ /** The report's first paragraph of prose, without Markdown marks. */
11
+ excerpt?: string;
12
+ words: number;
13
+ minutes: number;
14
+ /** Its `##` and `###` headings, in order. */
15
+ headings: string[];
16
+ sections: number;
17
+ /** Distinct web addresses it cites. */
18
+ sources: number;
19
+ }
20
+ /** `jacqueline-m-quinless-…-261029-0e1f…` → `jacqueline m quinless …`: the date and uuid a folder name ends in are not its name. */
21
+ export declare function folderTitle(name: string): string;
22
+ export declare function digestReport(markdown: string, folder: string): ReportDigest;
23
+ /**
24
+ * The text as the page shows it. A stray fence would turn the rest of a report into one code box, so
25
+ * a fence with no close in reach is left out of the display. The file itself is never changed.
26
+ */
27
+ export declare function readableMarkdown(markdown: string): {
28
+ markdown: string;
29
+ repaired: boolean;
30
+ };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * What a shelf report is, read from its own Markdown, for a shelf a person can look through: its
3
+ * title, its opening, how long it is, what is inside, how many sources it cites. Everything here is
4
+ * computed from the file; nothing is summarised or invented. jgwill/Miadi#879.
5
+ */
6
+ const WORDS_PER_MINUTE = 230;
7
+ const MAX_HEADINGS = 6;
8
+ const MAX_EXCERPT = 280;
9
+ /** A stray fence must not swallow a report: a block longer than this is not treated as code. */
10
+ const MAX_FENCE_LINES = 60;
11
+ const MAX_TITLE = 100;
12
+ /** `jacqueline-m-quinless-…-261029-0e1f…` → `jacqueline m quinless …`: the date and uuid a folder name ends in are not its name. */
13
+ export function folderTitle(name) {
14
+ const bare = name
15
+ .replace(/^ep\d+[-_]/i, "")
16
+ .replace(/-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "")
17
+ .replace(/-\d{6,10}(-[0-9a-f]{6,})?$/i, "")
18
+ .replace(/[-_]+/g, " ")
19
+ .trim();
20
+ return bare ? bare[0].toUpperCase() + bare.slice(1) : name;
21
+ }
22
+ function plain(text) {
23
+ return text
24
+ .replace(/!\[[^\]]*\]\([^)]*\)/g, "")
25
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
26
+ .replace(/[`*_~]+/g, "")
27
+ .replace(/<[^>]+>/g, "")
28
+ .replace(/\s+/g, " ")
29
+ .trim();
30
+ }
31
+ /** The lines outside fenced code, with a fence left alone when no close comes within reach. */
32
+ function outsideFences(lines) {
33
+ const out = [];
34
+ for (let i = 0; i < lines.length; i++) {
35
+ const open = /^(```|~~~)/.exec(lines[i]);
36
+ if (!open) {
37
+ out.push(lines[i]);
38
+ continue;
39
+ }
40
+ const close = lines.slice(i + 1, i + 1 + MAX_FENCE_LINES).findIndex((l) => l.trimEnd() === open[1]);
41
+ if (close < 0)
42
+ continue;
43
+ i += close + 1;
44
+ }
45
+ return out;
46
+ }
47
+ /** A heading that is a prompt, a command or a chat fragment is not the document's name. */
48
+ const nameable = (t) => t.length > 0 && t.length <= MAX_TITLE && !/^@/.test(t) && !/\s--[a-z]/.test(t) && !/^(hey|hi|ok|okay|so|please|can you|could you)\b/i.test(t);
49
+ /** How a document was made is not what it is about. */
50
+ const PROVENANCE = /^(parent vessel|inquiry vessel|author role|mission|seed for|for the author's review|rendered from|status:|date:|woven to|generated)\b|\/(srv|workspace|a\/src|home)\/|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
51
+ export function digestReport(markdown, folder) {
52
+ const clean = outsideFences(markdown.replace(/^---\n[\s\S]*?\n---\n/, "").split("\n"));
53
+ const body = clean.join("\n");
54
+ const h1 = clean.find((l) => /^#\s+\S/.test(l));
55
+ const h1Text = h1 ? plain(h1.replace(/^#\s+/, "")) : "";
56
+ const headings = clean.filter((l) => /^#{2,3}\s+\S/.test(l)).map((l) => plain(l.replace(/^#+\s+/, "")));
57
+ let excerpt;
58
+ for (const block of body.split(/\n\s*\n/)) {
59
+ const t = block.trim();
60
+ if (!t || /^(#|\||>|[-*+]\s|\d+\.\s|!\[|<|---|===)/.test(t))
61
+ continue;
62
+ const p = plain(t);
63
+ if (p.split(" ").length < 8 || PROVENANCE.test(p))
64
+ continue;
65
+ excerpt = p.length > MAX_EXCERPT ? `${p.slice(0, MAX_EXCERPT).replace(/\s+\S*$/, "")}…` : p;
66
+ break;
67
+ }
68
+ const words = plain(body.replace(/^#+\s+/gm, "")).split(" ").filter(Boolean).length;
69
+ const titled = nameable(h1Text);
70
+ return {
71
+ title: titled ? h1Text : folderTitle(folder),
72
+ titled,
73
+ ...(excerpt ? { excerpt } : {}),
74
+ words,
75
+ minutes: Math.max(1, Math.round(words / WORDS_PER_MINUTE)),
76
+ headings: headings.slice(0, MAX_HEADINGS),
77
+ sections: headings.length,
78
+ sources: new Set(markdown.match(/https?:\/\/[^\s)\]>"']+/g) ?? []).size,
79
+ };
80
+ }
81
+ /**
82
+ * The text as the page shows it. A stray fence would turn the rest of a report into one code box, so
83
+ * a fence with no close in reach is left out of the display. The file itself is never changed.
84
+ */
85
+ export function readableMarkdown(markdown) {
86
+ const lines = markdown.split("\n");
87
+ const out = [];
88
+ let repaired = false;
89
+ for (let i = 0; i < lines.length; i++) {
90
+ const open = /^(```|~~~)/.exec(lines[i]);
91
+ if (!open) {
92
+ out.push(lines[i]);
93
+ continue;
94
+ }
95
+ const close = lines.slice(i + 1, i + 1 + MAX_FENCE_LINES).findIndex((l) => l.trimEnd() === open[1]);
96
+ if (close < 0) {
97
+ repaired = true;
98
+ continue;
99
+ }
100
+ out.push(...lines.slice(i, i + close + 2));
101
+ i += close + 1;
102
+ }
103
+ return { markdown: out.join("\n"), repaired };
104
+ }
@@ -0,0 +1,173 @@
1
+ import { type ArtefactWeave } from "@miadi/inquiry-weave";
2
+ import { type ReportDigest } from "./research-digest.js";
3
+ /** A saved report may be 1 MB of characters; several times that is not a document anyone reads through a tool. */
4
+ export declare const MAX_SHELF_FILE_BYTES: number;
5
+ export declare class ShelfRefusal extends Error {
6
+ readonly code: "invalid_ref" | "not_found" | "forbidden";
7
+ constructor(code: "invalid_ref" | "not_found" | "forbidden", message: string);
8
+ }
9
+ /** Who may read a folder. `weave` is undefined for a folder no one saved through a door. */
10
+ export declare function canReadShelf(weave: ArtefactWeave | undefined, person: {
11
+ id: string;
12
+ api_tier: string | null;
13
+ }): boolean;
14
+ export interface ShelfReadOptions {
15
+ file?: string;
16
+ offset?: number;
17
+ revision?: string;
18
+ maxBytes?: number;
19
+ }
20
+ /** The folder's manifest, and one file's page when a file is named or the folder holds its report. */
21
+ export declare function readShelfArtefact(root: string, name: string, person: {
22
+ id: string;
23
+ api_tier: string | null;
24
+ }, opts?: ShelfReadOptions): Promise<{
25
+ note: string;
26
+ files_truncated?: string | undefined;
27
+ episodes: string[];
28
+ git: {
29
+ commit?: string;
30
+ committed_at?: string;
31
+ uncommitted_changes?: boolean;
32
+ state: "known" | "unknown";
33
+ };
34
+ files: {
35
+ path: string;
36
+ bytes: number;
37
+ sha256?: string;
38
+ readable: boolean;
39
+ }[];
40
+ saved?: {
41
+ file: string;
42
+ sha256: string;
43
+ bytes: number;
44
+ saved_at: string;
45
+ via: string | undefined;
46
+ refs: string[];
47
+ } | undefined;
48
+ issue?: string | undefined;
49
+ issue_url?: string | undefined;
50
+ circle?: string | undefined;
51
+ visibility: "circle" | "published" | "private" | "proposed";
52
+ owner?: {
53
+ person_id: string;
54
+ name: string | undefined;
55
+ } | undefined;
56
+ artefact: string;
57
+ } | {
58
+ page: {
59
+ offset: number;
60
+ end: number;
61
+ bytes: number;
62
+ sha256: string;
63
+ complete: boolean;
64
+ next: {
65
+ offset: number;
66
+ revision: string;
67
+ } | null;
68
+ };
69
+ text: string;
70
+ modified_at?: string | undefined;
71
+ verification?: string | undefined;
72
+ bytes: number;
73
+ sha256: string;
74
+ verified: boolean;
75
+ file: string;
76
+ content_type: string;
77
+ integrity: string;
78
+ file_git: {
79
+ commit?: string;
80
+ committed_at?: string;
81
+ uncommitted_changes?: boolean;
82
+ state: "known" | "unknown";
83
+ };
84
+ files_truncated?: string | undefined;
85
+ episodes: string[];
86
+ git: {
87
+ commit?: string;
88
+ committed_at?: string;
89
+ uncommitted_changes?: boolean;
90
+ state: "known" | "unknown";
91
+ };
92
+ files: {
93
+ path: string;
94
+ bytes: number;
95
+ sha256?: string;
96
+ readable: boolean;
97
+ }[];
98
+ saved?: {
99
+ file: string;
100
+ sha256: string;
101
+ bytes: number;
102
+ saved_at: string;
103
+ via: string | undefined;
104
+ refs: string[];
105
+ } | undefined;
106
+ issue?: string | undefined;
107
+ issue_url?: string | undefined;
108
+ circle?: string | undefined;
109
+ visibility: "circle" | "published" | "private" | "proposed";
110
+ owner?: {
111
+ person_id: string;
112
+ name: string | undefined;
113
+ } | undefined;
114
+ artefact: string;
115
+ }>;
116
+ /** One thing on the shelf, as its own files say it is: computed from them, never summarised. */
117
+ export interface ShelfListing extends ReportDigest {
118
+ ref: string;
119
+ artefact: string;
120
+ /** `report`: saved through a door, with its hash. `folder`: anything else with a document. `notes`: agent instructions or unreadable files only. */
121
+ kind: "report" | "folder" | "notes";
122
+ /** The document its title and opening come from, the one a reader opens on. */
123
+ main?: string;
124
+ files: number;
125
+ /** Saved, or last woven, or its main document's last change. */
126
+ at?: string;
127
+ owner?: {
128
+ person_id: string;
129
+ name?: string;
130
+ };
131
+ visibility: string;
132
+ circle?: string;
133
+ issue?: string;
134
+ issue_url?: string;
135
+ episodes: {
136
+ number?: number;
137
+ title: string;
138
+ }[];
139
+ saved?: {
140
+ sha256: string;
141
+ bytes: number;
142
+ saved_at: string;
143
+ via?: string;
144
+ refs: string[];
145
+ };
146
+ }
147
+ /** One folder's listing. A folder that cannot be read is listed by its name. */
148
+ export declare function shelfListing(root: string, name: string, weave: ArtefactWeave | undefined): ShelfListing;
149
+ export type ShelfSort = "newest" | "oldest" | "longest" | "title";
150
+ export declare function sortListings(items: ShelfListing[], sort?: ShelfSort): ShelfListing[];
151
+ /**
152
+ * The shelf as this person may read it. `mine` is what they own. `all` is every folder, the write
153
+ * tier's alone, because a folder another person owns or no one owns is theirs to read and no one else's.
154
+ */
155
+ export declare function listShelfArtefacts(root: string, person: {
156
+ id: string;
157
+ api_tier: string | null;
158
+ }, opts?: {
159
+ scope?: "mine" | "all";
160
+ query?: string;
161
+ kind?: ShelfListing["kind"];
162
+ sort?: ShelfSort;
163
+ limit?: number;
164
+ offset?: number;
165
+ }): Promise<{
166
+ scope: "mine" | "all";
167
+ total: number;
168
+ offset: number;
169
+ limit: number;
170
+ items: ShelfListing[];
171
+ next_offset: number | null;
172
+ read_with: string;
173
+ }>;