@miadi/connector 0.1.19 → 0.1.21

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.
Files changed (43) hide show
  1. package/README.md +40 -5
  2. package/dist/approve.d.ts +16 -2
  3. package/dist/approve.js +185 -16
  4. package/dist/bin.js +3 -0
  5. package/dist/mission-status.d.ts +13 -1
  6. package/dist/mission-status.js +13 -5
  7. package/dist/model-preflight.d.ts +9 -2
  8. package/dist/model-preflight.js +23 -1
  9. package/dist/provider-policy.d.ts +94 -0
  10. package/dist/provider-policy.js +150 -0
  11. package/dist/providers.d.ts +7 -3
  12. package/dist/providers.js +13 -7
  13. package/dist/queue-state.js +12 -1
  14. package/dist/reading/adapters.d.ts +18 -0
  15. package/dist/reading/adapters.js +90 -0
  16. package/dist/reading/contract.d.ts +153 -0
  17. package/dist/reading/contract.js +79 -0
  18. package/dist/reading/markdown.d.ts +47 -0
  19. package/dist/reading/markdown.js +52 -0
  20. package/dist/reading/privacy.d.ts +7 -0
  21. package/dist/reading/privacy.js +14 -0
  22. package/dist/reading/workflow.d.ts +93 -0
  23. package/dist/reading/workflow.js +325 -0
  24. package/dist/records.d.ts +1 -1
  25. package/dist/records.js +6 -0
  26. package/dist/request-state.js +9 -1
  27. package/dist/research-digest.d.ts +30 -0
  28. package/dist/research-digest.js +104 -0
  29. package/dist/research-shelf.d.ts +173 -0
  30. package/dist/research-shelf.js +297 -0
  31. package/dist/runtime-steer.d.ts +182 -0
  32. package/dist/runtime-steer.js +543 -0
  33. package/dist/server.d.ts +14 -0
  34. package/dist/server.js +4 -12
  35. package/dist/steer.d.ts +21 -7
  36. package/dist/steer.js +115 -10
  37. package/dist/store.d.ts +2 -0
  38. package/dist/store.js +18 -1
  39. package/dist/tools.d.ts +29 -1
  40. package/dist/tools.js +435 -50
  41. package/dist/version.d.ts +1 -1
  42. package/dist/version.js +1 -1
  43. package/package.json +13 -1
@@ -0,0 +1,153 @@
1
+ /** Bounded reading contract. Product state lives in the connector ledger; providers stay injected. */
2
+ import { z } from "zod";
3
+ export declare const hash: (value: string) => string;
4
+ export declare const readingScope: z.ZodObject<{
5
+ source_intent: z.ZodString;
6
+ revision_of: z.ZodOptional<z.ZodString>;
7
+ kind: z.ZodDefault<z.ZodEnum<{
8
+ "research-inquiry": "research-inquiry";
9
+ "ui-screenwalk": "ui-screenwalk";
10
+ }>>;
11
+ language: z.ZodString;
12
+ rights: z.ZodString;
13
+ providers: z.ZodArray<z.ZodString>;
14
+ max_cost_usd: z.ZodDefault<z.ZodNumber>;
15
+ budget_approval_ref: z.ZodOptional<z.ZodString>;
16
+ allow_media_access: z.ZodDefault<z.ZodBoolean>;
17
+ allow_external_processing: z.ZodDefault<z.ZodBoolean>;
18
+ max_field_words: z.ZodDefault<z.ZodNumber>;
19
+ max_fields: z.ZodDefault<z.ZodNumber>;
20
+ }, z.core.$strict>;
21
+ export type ReadingScope = z.infer<typeof readingScope>;
22
+ export declare const sourcePart: z.ZodObject<{
23
+ video_id: z.ZodString;
24
+ kind: z.ZodEnum<{
25
+ captions: "captions";
26
+ verbatim_transcript: "verbatim_transcript";
27
+ }>;
28
+ channel: z.ZodEnum<{
29
+ video: "video";
30
+ commentary: "commentary";
31
+ }>;
32
+ text: z.ZodString;
33
+ normalized_text: z.ZodOptional<z.ZodString>;
34
+ language: z.ZodString;
35
+ locator: z.ZodString;
36
+ provider: z.ZodString;
37
+ caption_kind: z.ZodEnum<{
38
+ manual: "manual";
39
+ automatic: "automatic";
40
+ supplied: "supplied";
41
+ local_stt: "local_stt";
42
+ }>;
43
+ fetched_at: z.ZodString;
44
+ rights: z.ZodString;
45
+ timestamp_granularity: z.ZodEnum<{
46
+ none: "none";
47
+ word: "word";
48
+ segment: "segment";
49
+ turn: "turn";
50
+ }>;
51
+ alignment_status: z.ZodEnum<{
52
+ unknown: "unknown";
53
+ verified: "verified";
54
+ }>;
55
+ speaker_status: z.ZodEnum<{
56
+ unknown: "unknown";
57
+ verified: "verified";
58
+ }>;
59
+ segments: z.ZodArray<z.ZodObject<{
60
+ start_ms: z.ZodNumber;
61
+ end_ms: z.ZodNumber;
62
+ text: z.ZodString;
63
+ speaker: z.ZodOptional<z.ZodString>;
64
+ }, z.core.$strict>>;
65
+ }, z.core.$strict>;
66
+ export type SourcePart = z.infer<typeof sourcePart>;
67
+ export interface Provider {
68
+ id: string;
69
+ available: boolean;
70
+ external: boolean;
71
+ media: boolean;
72
+ max_cost_usd: number;
73
+ method: string;
74
+ }
75
+ export declare const evidence: z.ZodObject<{
76
+ category: z.ZodEnum<{
77
+ transcript_supported: "transcript_supported";
78
+ visually_evidenced: "visually_evidenced";
79
+ inference: "inference";
80
+ open_question: "open_question";
81
+ }>;
82
+ claim: z.ZodString;
83
+ at_ms: z.ZodOptional<z.ZodNumber>;
84
+ visual_receipt: z.ZodOptional<z.ZodString>;
85
+ }, z.core.$strict>;
86
+ export declare const baseReview: z.ZodObject<{
87
+ markdown: z.ZodString;
88
+ claims: z.ZodArray<z.ZodObject<{
89
+ category: z.ZodEnum<{
90
+ transcript_supported: "transcript_supported";
91
+ visually_evidenced: "visually_evidenced";
92
+ inference: "inference";
93
+ open_question: "open_question";
94
+ }>;
95
+ claim: z.ZodString;
96
+ at_ms: z.ZodOptional<z.ZodNumber>;
97
+ visual_receipt: z.ZodOptional<z.ZodString>;
98
+ }, z.core.$strict>>;
99
+ model: z.ZodString;
100
+ }, z.core.$strict>;
101
+ export declare const survey: z.ZodObject<{
102
+ fields: z.ZodArray<z.ZodObject<{
103
+ name: z.ZodString;
104
+ body: z.ZodString;
105
+ at_ms: z.ZodNumber;
106
+ question: z.ZodString;
107
+ limitation: z.ZodString;
108
+ source_ids: z.ZodArray<z.ZodString>;
109
+ }, z.core.$strict>>;
110
+ sources: z.ZodArray<z.ZodObject<{
111
+ id: z.ZodString;
112
+ title: z.ZodString;
113
+ url: z.ZodString;
114
+ excerpt: z.ZodString;
115
+ claim: z.ZodString;
116
+ }, z.core.$strict>>;
117
+ model: z.ZodString;
118
+ }, z.core.$strict>;
119
+ export declare const reflection: z.ZodObject<{
120
+ dialogue: z.ZodArray<z.ZodObject<{
121
+ voice: z.ZodEnum<{
122
+ Ava: "Ava";
123
+ Mia: "Mia";
124
+ }>;
125
+ text: z.ZodString;
126
+ }, z.core.$strict>>;
127
+ synthesis: z.ZodString;
128
+ methods: z.ZodArray<z.ZodObject<{
129
+ question: z.ZodString;
130
+ method: z.ZodString;
131
+ evidence: z.ZodString;
132
+ contradiction: z.ZodString;
133
+ }, z.core.$strict>>;
134
+ hypothesis: z.ZodString;
135
+ model: z.ZodString;
136
+ }, z.core.$strict>;
137
+ export declare const suppliedDialogue: z.ZodObject<{
138
+ text: z.ZodString;
139
+ author: z.ZodString;
140
+ tool: z.ZodString;
141
+ supplied_at: z.ZodString;
142
+ }, z.core.$strict>;
143
+ export type BaseReview = z.infer<typeof baseReview>;
144
+ export type Survey = z.infer<typeof survey>;
145
+ export type Reflection = z.infer<typeof reflection>;
146
+ export declare class ReadingBlocked extends Error {
147
+ readonly status: string;
148
+ readonly next_action: string;
149
+ constructor(status: string, next_action: string);
150
+ }
151
+ export declare function block(status: string, next: string): never;
152
+ /** Recognise only real YouTube hosts and exact ids; never use URL-derived text as code. */
153
+ export declare function canonicalVideo(input: string): string;
@@ -0,0 +1,79 @@
1
+ /** Bounded reading contract. Product state lives in the connector ledger; providers stay injected. */
2
+ import { z } from "zod";
3
+ import { createHash } from "node:crypto";
4
+ export const hash = (value) => createHash("sha256").update(value).digest("hex");
5
+ export const readingScope = z.object({
6
+ source_intent: z.string().regex(/^source_intent:[\da-f-]{36}$/i),
7
+ revision_of: z.string().regex(/^reading:[a-f\d]{32}$/).optional(),
8
+ kind: z.enum(["research-inquiry", "ui-screenwalk"]).default("research-inquiry"),
9
+ language: z.string().min(2).max(40),
10
+ rights: z.string().min(1).max(1000),
11
+ providers: z.array(z.string().min(1).max(100)).min(1).max(20),
12
+ max_cost_usd: z.number().finite().min(0).max(100).default(0),
13
+ budget_approval_ref: z.string().min(1).optional(),
14
+ allow_media_access: z.boolean().default(false),
15
+ allow_external_processing: z.boolean().default(false),
16
+ max_field_words: z.number().int().min(20).max(500).default(55),
17
+ max_fields: z.number().int().min(1).max(12).default(6),
18
+ }).strict();
19
+ export const sourcePart = z.object({
20
+ video_id: z.string().regex(/^[\w-]{11}$/),
21
+ kind: z.enum(["captions", "verbatim_transcript"]),
22
+ channel: z.enum(["video", "commentary"]),
23
+ text: z.string().min(1).max(800000),
24
+ normalized_text: z.string().max(800000).optional(),
25
+ language: z.string().min(2),
26
+ locator: z.string().min(1),
27
+ provider: z.string().min(1),
28
+ caption_kind: z.enum(["manual", "automatic", "supplied", "local_stt"]),
29
+ fetched_at: z.string().datetime(),
30
+ rights: z.string().min(1),
31
+ timestamp_granularity: z.enum(["word", "segment", "turn", "none"]),
32
+ alignment_status: z.enum(["unknown", "verified"]),
33
+ speaker_status: z.enum(["unknown", "verified"]),
34
+ segments: z.array(z.object({ start_ms: z.number().int().min(0), end_ms: z.number().int().min(0), text: z.string(), speaker: z.string().optional() }).strict()).max(50000),
35
+ }).strict();
36
+ export const evidence = z.object({
37
+ category: z.enum(["transcript_supported", "visually_evidenced", "inference", "open_question"]),
38
+ claim: z.string().min(1),
39
+ at_ms: z.number().int().min(0).optional(),
40
+ visual_receipt: z.string().min(1).optional(),
41
+ }).strict();
42
+ export const baseReview = z.object({ markdown: z.string().min(1).max(400000), claims: z.array(evidence).min(1), model: z.string().min(1) }).strict();
43
+ export const survey = z.object({
44
+ fields: z.array(z.object({ name: z.string().min(1), body: z.string().min(1), at_ms: z.number().int().min(0), question: z.string().min(1), limitation: z.string().min(1), source_ids: z.array(z.string()).min(1) }).strict()).min(1),
45
+ sources: z.array(z.object({ id: z.string().regex(/^[A-Za-z0-9_-]{1,40}$/), title: z.string().trim().min(1), url: z.string().url(), excerpt: z.string().min(20).max(1000).refine(s => s.trim().length >= 20, "A verifiable excerpt needs meaningful text"), claim: z.string().trim().min(1) }).strict()).min(1),
46
+ model: z.string().min(1),
47
+ }).strict();
48
+ export const reflection = z.object({
49
+ dialogue: z.array(z.object({ voice: z.enum(["Ava", "Mia"]), text: z.string().min(1) }).strict()).min(2),
50
+ synthesis: z.string().min(40),
51
+ methods: z.array(z.object({ question: z.string().min(1), method: z.string().min(1), evidence: z.string().min(1), contradiction: z.string().min(1) }).strict()).min(1),
52
+ hypothesis: z.string().min(1), model: z.string().min(1),
53
+ }).strict();
54
+ export const suppliedDialogue = z.object({ text: z.string().min(1).max(200000), author: z.string().min(1), tool: z.string().min(1), supplied_at: z.string().datetime() }).strict();
55
+ export class ReadingBlocked extends Error {
56
+ status;
57
+ next_action;
58
+ constructor(status, next_action) {
59
+ super(status);
60
+ this.status = status;
61
+ this.next_action = next_action;
62
+ }
63
+ }
64
+ export function block(status, next) { throw new ReadingBlocked(status, next); }
65
+ /** Recognise only real YouTube hosts and exact ids; never use URL-derived text as code. */
66
+ export function canonicalVideo(input) {
67
+ if (/^[\w-]{11}$/.test(input))
68
+ return input;
69
+ try {
70
+ const u = new URL(input);
71
+ if (u.protocol !== "https:")
72
+ throw new Error();
73
+ const id = u.hostname === "youtu.be" ? u.pathname.slice(1) : ["youtube.com", "www.youtube.com", "m.youtube.com"].includes(u.hostname) ? u.searchParams.get("v") ?? u.pathname.match(/^\/(?:shorts|embed)\/([\w-]+)$/)?.[1] : undefined;
74
+ if (id && /^[\w-]{11}$/.test(id))
75
+ return id;
76
+ }
77
+ catch { /* fail closed */ }
78
+ return block("blocked_permission", "Supply an exact YouTube video id or supported HTTPS URL.");
79
+ }
@@ -0,0 +1,47 @@
1
+ import { type Reflection, type Survey } from "./contract.js";
2
+ /** UTF-16 string offsets outside code fences, retaining CRLF. No Markdown reserialization. */
3
+ export declare function headings(markdown: string): {
4
+ name: string;
5
+ at: number;
6
+ }[];
7
+ export declare function insert(base: string, at: number, addition: string): {
8
+ markdown: string;
9
+ diff: {
10
+ operation: string;
11
+ at: number;
12
+ addition: string;
13
+ base_sha256: string;
14
+ sha256: string;
15
+ bytes: number;
16
+ retained: boolean;
17
+ };
18
+ };
19
+ export declare function academic(base: string, s: Survey): {
20
+ markdown: string;
21
+ diffs: {
22
+ operation: string;
23
+ at: number;
24
+ addition: string;
25
+ base_sha256: string;
26
+ sha256: string;
27
+ bytes: number;
28
+ retained: boolean;
29
+ }[];
30
+ };
31
+ export declare function reflective(base: string, r: Reflection, companion?: {
32
+ text: string;
33
+ author: string;
34
+ tool: string;
35
+ supplied_at: string;
36
+ }): {
37
+ markdown: string;
38
+ diff: {
39
+ operation: string;
40
+ at: number;
41
+ addition: string;
42
+ base_sha256: string;
43
+ sha256: string;
44
+ bytes: number;
45
+ retained: boolean;
46
+ };
47
+ };
@@ -0,0 +1,52 @@
1
+ import { block, hash } from "./contract.js";
2
+ /** UTF-16 string offsets outside code fences, retaining CRLF. No Markdown reserialization. */
3
+ export function headings(markdown) {
4
+ let offset = 0, fence = null, fenceLength = 0;
5
+ const out = [];
6
+ for (const line of markdown.match(/[^\n]*\n|[^\n]+$/g) ?? []) {
7
+ const f = line.match(/^ {0,3}(`{3,}|~{3,})/);
8
+ if (f) {
9
+ if (!fence) {
10
+ fence = f[1][0];
11
+ fenceLength = f[1].length;
12
+ }
13
+ else if (fence === f[1][0] && f[1].length >= fenceLength)
14
+ fence = null;
15
+ }
16
+ else if (!fence) {
17
+ const h = line.match(/^## ([^\r\n]+?)\s*#*\s*$/);
18
+ if (h)
19
+ out.push({ name: h[1].trim(), at: offset });
20
+ }
21
+ offset += line.length;
22
+ }
23
+ if (fence)
24
+ block("blocked_citations", "The base contains an unclosed Markdown fence. Ask the author to approve its repair before enrichment.");
25
+ return out;
26
+ }
27
+ export function insert(base, at, addition) {
28
+ const markdown = base.slice(0, at) + addition + base.slice(at);
29
+ return { markdown, diff: { operation: "insert", at, addition, base_sha256: hash(base), sha256: hash(markdown), bytes: Buffer.byteLength(markdown), retained: true } };
30
+ }
31
+ export function academic(base, s) {
32
+ const hs = headings(base), summary = hs.findIndex(h => h.name.toLowerCase() === "summary");
33
+ if (summary < 0 || hs.filter(h => h.name.toLowerCase() === "summary").length !== 1 || hs.some(h => /^(academic fields|academic field survey)$/i.test(h.name)))
34
+ block("blocked_citations", "A unique Summary and no existing Academic Fields are required; resolve the base with the author.");
35
+ const at = hs[summary + 1]?.at ?? base.length;
36
+ const fields = "\n\n## Academic Fields\n\n" + s.fields.map(f => `### ${f.name}\n\n${f.body}\n\nSource anchor: ${f.at_ms} ms (transcript). External literature: ${f.source_ids.map(id => `[${id}]`).join(", ")}.\n\nOpen question: ${f.question}\n\nLimit: ${f.limitation}\n`).join("\n") + "\n";
37
+ const first = insert(base, at, fields);
38
+ const sources = "\n\n## Reading Sources\n\n" + s.sources.map(x => `- [${x.id}] ${x.title}. ${x.url}\n Retrieved excerpt checked; relevance to the inquiry is an agent interpretation. This receipt does not claim a full reading.\n`).join("\n");
39
+ const second = insert(first.markdown, first.markdown.length, sources);
40
+ return { markdown: second.markdown, diffs: [first.diff, second.diff] };
41
+ }
42
+ export function reflective(base, r, companion) {
43
+ let added = `\n\n## Reflective Methodology — AI-generated Ava–Mia Dialogue\n\nGenerated by ${r.model}; fictional agent voices reflecting on this survey. These words were not spoken in the recording or supplied by the author.\n\n`;
44
+ added += r.dialogue.map(d => `**${d.voice} (AI-generated):** ${d.text}\n`).join("\n");
45
+ added += `\n### Cross-field synthesis (interpretation)\n\n${r.synthesis}\n\n### Proposed methods and possible evidence\n\n`;
46
+ added += r.methods.map(m => `- Question: ${m.question}\n Method (proposed): ${m.method}\n Possible evidence: ${m.evidence}\n Contradiction to test: ${m.contradiction}\n`).join("\n");
47
+ added += `\nHypothesis (untested): ${r.hypothesis}\n\n`;
48
+ if (companion)
49
+ added += `## Author-supplied Companion Dialogue\n\nSeparate contribution; supplied by ${companion.author}, originating tool ${companion.tool}, ${companion.supplied_at}. Alignment with video unknown. Exact supplied text follows.\n\n${companion.text}\n\n`;
50
+ const at = headings(base).find(h => h.name === "Reading Sources")?.at ?? base.length;
51
+ return insert(base, at, added);
52
+ }
@@ -0,0 +1,7 @@
1
+ import type { ConnectorRecord } from "../records.js";
2
+ /** Processing approval never grants other readers access to drafts or completion text. */
3
+ export declare function readingOwner(record: ConnectorRecord, records: ConnectorRecord[]): string | undefined;
4
+ export declare function canReadReading(record: ConnectorRecord, records: ConnectorRecord[], viewer: {
5
+ id: string;
6
+ api_tier?: string | null;
7
+ }): boolean;
@@ -0,0 +1,14 @@
1
+ /** Processing approval never grants other readers access to drafts or completion text. */
2
+ export function readingOwner(record, records) {
3
+ if (record.body.reading || record.body.reading_import)
4
+ return record.actor.person_id;
5
+ if (record.kind === "session_completion_proposal") {
6
+ const parent = records.find(r => r.kind === "run_request" && (r.body.reading || r.body.reading_import) && (r.id === record.body.mission || record.refs.includes(r.id)));
7
+ return parent?.actor.person_id;
8
+ }
9
+ return undefined;
10
+ }
11
+ export function canReadReading(record, records, viewer) {
12
+ const owner = readingOwner(record, records);
13
+ return !owner || viewer.api_tier === "write" || owner === viewer.id;
14
+ }
@@ -0,0 +1,93 @@
1
+ import { type Actor, type ConnectorRecord } from "../records.js";
2
+ import { Ledger } from "../store.js";
3
+ import { type Provider, type ReadingScope, type SourcePart, type BaseReview, type Survey, type Reflection } from "./contract.js";
4
+ export interface ReviewBase {
5
+ id: string;
6
+ version: number;
7
+ markdown: string;
8
+ video_id: string;
9
+ }
10
+ export interface StageContext {
11
+ workflow_id: string;
12
+ video_id: string;
13
+ scope: ReadingScope;
14
+ idempotency_key: string;
15
+ transcript: SourcePart;
16
+ source_part_id: string;
17
+ base?: BaseReview;
18
+ survey?: Survey;
19
+ }
20
+ export interface ReadingAdapters {
21
+ /** Provider execution belongs to the runtime/shim. Every operation must replay a key without charging twice. */
22
+ providers: Record<"transcript" | "v1" | "academic" | "reflective", Provider & {
23
+ idempotent: true;
24
+ }>;
25
+ lookup(video: string): Promise<ReviewBase | null>;
26
+ acquire(context: Omit<StageContext, "transcript" | "source_part_id">): Promise<SourcePart | null>;
27
+ generateV1(context: StageContext): Promise<BaseReview>;
28
+ generateSurvey(context: StageContext): Promise<Survey>;
29
+ generateReflection(context: StageContext): Promise<Reflection>;
30
+ /** Read primary sources through a host-controlled allowlist; no source-directed credential or media forwarding. */
31
+ verifySource(source: Survey["sources"][number]): Promise<{
32
+ url: string;
33
+ text: string;
34
+ fetched_at: string;
35
+ }>;
36
+ /** True only after a real capability handshake for atomic compare-and-swap + durable idempotency. */
37
+ atomicImport: boolean;
38
+ importExact(input: {
39
+ video_id: string;
40
+ review_id: string | null;
41
+ expected_version: number;
42
+ expected_sha256: string;
43
+ markdown: string;
44
+ sha256: string;
45
+ idempotency_key: string;
46
+ }): Promise<{
47
+ id: string;
48
+ version: number;
49
+ }>;
50
+ readExact(id: string, version: number): Promise<ReviewBase>;
51
+ }
52
+ type Receipt = {
53
+ stage: string;
54
+ status: string;
55
+ attempt: number;
56
+ started_at: string;
57
+ completed_at: string;
58
+ workflow_id: string;
59
+ request_id: string;
60
+ video_id: string;
61
+ actor: Actor;
62
+ consent_scope: ReadingScope;
63
+ approval_state: string;
64
+ artifact_refs: string[];
65
+ sha256: string;
66
+ previous_sha256: string | null;
67
+ artifact: any;
68
+ next_action: string;
69
+ };
70
+ export type ReadingResult = {
71
+ workflow_id: string;
72
+ status: string;
73
+ next_action: string;
74
+ receipts: Receipt[];
75
+ };
76
+ /** One workflow per person and video; two concurrent releases cannot spend twice. */
77
+ export declare function workflowId(actor: string, video: string, revisionOf?: string): string;
78
+ export declare class ReadingWorkflow {
79
+ readonly stateDir: string;
80
+ readonly adapters: ReadingAdapters;
81
+ readonly ledger: Ledger<ConnectorRecord>;
82
+ constructor(stateDir: string, adapters: ReadingAdapters);
83
+ receipts(id: string): Receipt[];
84
+ private approved;
85
+ private source;
86
+ private locked;
87
+ private record;
88
+ private done;
89
+ private provider;
90
+ run(requestId: string, companion?: unknown): Promise<ReadingResult>;
91
+ importApproved(requestId: string): Promise<ReadingResult>;
92
+ }
93
+ export {};