@unson/brainbase-mcp 0.44.2 → 0.46.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/README.md +2 -2
- package/contracts/judgment-dag/digest.json +2 -2
- package/dist/automation-run.d.ts +3 -12
- package/dist/automation-run.js +2 -5
- package/dist/canonical-task-postgres.d.ts +6 -0
- package/dist/canonical-task-postgres.js +44 -4
- package/dist/canonical-task-service.d.ts +16 -1
- package/dist/canonical-task-service.js +28 -9
- package/dist/meeting-source-mcp-adapters.d.ts +76 -0
- package/dist/meeting-source-mcp-adapters.js +294 -0
- package/dist/meeting-source-sync-common-adapter.d.ts +19 -0
- package/dist/meeting-source-sync-common-adapter.js +34 -0
- package/dist/mesh.d.ts +10 -0
- package/dist/mesh.js +44 -13
- package/dist/routine-personal-kg.d.ts +43 -0
- package/dist/routine-personal-kg.js +65 -0
- package/dist/routine-production-ports.d.ts +30 -0
- package/dist/routine-production-ports.js +34 -0
- package/dist/routine-runtime.d.ts +3 -12
- package/dist/routine-runtime.js +2 -5
- package/dist/routines.d.ts +2 -0
- package/dist/routines.js +2 -0
- package/dist/run-receipt-port.d.ts +27 -0
- package/dist/run-receipt-port.js +12 -0
- package/package.json +14 -2
package/README.md
CHANGED
|
@@ -247,8 +247,8 @@ Publication is serialized by the GitHub workflow. Direct local `release:publish`
|
|
|
247
247
|
Dispatch the reviewed ref once when automatic publication needs recovery:
|
|
248
248
|
|
|
249
249
|
```bash
|
|
250
|
-
RELEASE_REF="<reviewed-
|
|
251
|
-
gh workflow run npm-publish.yml --ref
|
|
250
|
+
RELEASE_REF="<reviewed-main-commit>"
|
|
251
|
+
gh workflow run npm-publish.yml --ref main -f release_ref="$RELEASE_REF"
|
|
252
252
|
```
|
|
253
253
|
|
|
254
254
|
After the workflow succeeds, verify npm `gitHead`, `dist.integrity`, and dist-tag, and verify that the GitHub Release targets the reviewed release commit. If npm already contains the version, treat that version as immutable; fix the source, increment the version, and run the reviewed workflow again.
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "README.md",
|
|
28
|
-
"sha256": "
|
|
28
|
+
"sha256": "38f2f0f783bba47f83950cdc286dc03a3e0ee6190b0fbb67d72002e0402c5805"
|
|
29
29
|
}
|
|
30
30
|
],
|
|
31
|
-
"digest": "
|
|
31
|
+
"digest": "fa45bdca9bc397be78314d8f24c5a383637e6c016c61d7635f85fc50e3c04800"
|
|
32
32
|
}
|
package/dist/automation-run.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type NormalizedRunReceipt } from './run-receipt.js';
|
|
2
|
+
import { type RunReceiptPersistenceInput, type RunReceiptPort } from './run-receipt-port.js';
|
|
2
3
|
import { type Awaitable, type RuntimeHostContext, type RuntimeHostPorts } from './runtime-host.js';
|
|
3
4
|
export type AutomationRunOperation = 'automation.start' | 'automation.retry';
|
|
4
5
|
export type AutomationRunPhase = 'claimed' | 'runner_started' | 'receipt_persisted' | 'completed' | 'retryable' | 'uncertain' | 'unknown';
|
|
@@ -46,18 +47,8 @@ export interface AutomationRunnerPort {
|
|
|
46
47
|
start(input: AutomationRunnerStartInput): Awaitable<AutomationRunnerResult>;
|
|
47
48
|
retry?(input: AutomationRunnerRetryInput): Awaitable<AutomationRunnerResult>;
|
|
48
49
|
}
|
|
49
|
-
export
|
|
50
|
-
|
|
51
|
-
normalized: NormalizedRunReceipt;
|
|
52
|
-
context: RuntimeHostContext;
|
|
53
|
-
}
|
|
54
|
-
export interface AutomationReceiptPort {
|
|
55
|
-
persist(input: AutomationReceiptPersistenceInput): Awaitable<unknown>;
|
|
56
|
-
query(input: {
|
|
57
|
-
request: AutomationRunRequest;
|
|
58
|
-
context: RuntimeHostContext;
|
|
59
|
-
}): Awaitable<unknown>;
|
|
60
|
-
}
|
|
50
|
+
export type AutomationReceiptPersistenceInput = RunReceiptPersistenceInput<RuntimeHostContext>;
|
|
51
|
+
export type AutomationReceiptPort = RunReceiptPort<AutomationRunRequest, RuntimeHostContext>;
|
|
61
52
|
export type AutomationRunClaim = {
|
|
62
53
|
status: 'claimed';
|
|
63
54
|
claimToken: string;
|
package/dist/automation-run.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { normalizeRunReceipt } from './run-receipt.js';
|
|
3
|
+
import { assertRunReceiptPort } from './run-receipt-port.js';
|
|
3
4
|
import { resolveRuntimeHostContext } from './runtime-host.js';
|
|
4
5
|
function assertRequest(request) {
|
|
5
6
|
if (!request || typeof request !== 'object') {
|
|
@@ -16,11 +17,7 @@ function assertPorts(ports) {
|
|
|
16
17
|
if (!ports.runner || typeof ports.runner !== 'object' || typeof ports.runner.start !== 'function') {
|
|
17
18
|
throw new Error('automation runner port with start is required');
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
|| typeof ports.receipt.persist !== 'function'
|
|
21
|
-
|| typeof ports.receipt.query !== 'function') {
|
|
22
|
-
throw new Error('automation receipt port with persist and query is required');
|
|
23
|
-
}
|
|
20
|
+
assertRunReceiptPort(ports.receipt, 'automation receipt');
|
|
24
21
|
if (!ports.runStore || typeof ports.runStore !== 'object'
|
|
25
22
|
|| typeof ports.runStore.claim !== 'function'
|
|
26
23
|
|| typeof ports.runStore.markRunnerStarted !== 'function'
|
|
@@ -127,7 +127,13 @@ export declare class CanonicalTaskPostgresRepository {
|
|
|
127
127
|
limit?: number;
|
|
128
128
|
}): Promise<(JsonRecord | null)[]>;
|
|
129
129
|
findByIdempotencyKey(key: string, storageScope?: string): Promise<JsonRecord | null>;
|
|
130
|
+
private buildInsert;
|
|
130
131
|
create(input: JsonRecord, storageScope?: string): Promise<JsonRecord>;
|
|
132
|
+
/**
|
|
133
|
+
* Create the Task and run its audit writer in one transaction. A throwing
|
|
134
|
+
* writer rolls back the Task insert; the caller maps the audit error.
|
|
135
|
+
*/
|
|
136
|
+
createWithAudit(input: JsonRecord, runAudit: (created: JsonRecord, client: CanonicalTaskPgClient) => Promise<void>, storageScope?: string): Promise<JsonRecord>;
|
|
131
137
|
update(taskId: string, patch: JsonRecord, storageScope?: string): Promise<JsonRecord>;
|
|
132
138
|
listPurposeLabels({ projectCode, assigneePersonId, limit }: {
|
|
133
139
|
projectCode: string;
|
|
@@ -418,8 +418,7 @@ export class CanonicalTaskPostgresRepository {
|
|
|
418
418
|
const result = await this.query('SELECT * FROM canonical_tasks WHERE idempotency_key = $1 LIMIT 1', [key]);
|
|
419
419
|
return this.normalize(result.rows[0] || null);
|
|
420
420
|
}
|
|
421
|
-
|
|
422
|
-
this.rejectStorageScope(storageScope);
|
|
421
|
+
buildInsert(input) {
|
|
423
422
|
const columns = [
|
|
424
423
|
['id', randomUUID()],
|
|
425
424
|
['title', input.title],
|
|
@@ -447,14 +446,55 @@ export class CanonicalTaskPostgresRepository {
|
|
|
447
446
|
['last_operation_key', input.last_operation_key ?? null],
|
|
448
447
|
['last_operation_fingerprint', input.last_operation_fingerprint ?? null],
|
|
449
448
|
];
|
|
450
|
-
|
|
449
|
+
return {
|
|
450
|
+
sql: `INSERT INTO canonical_tasks (${columns.map(([column]) => column).join(', ')})
|
|
451
451
|
VALUES (${columns.map(([, , cast = ''], index) => `$${index + 1}${cast}`).join(', ')})
|
|
452
452
|
ON CONFLICT (idempotency_key) DO NOTHING
|
|
453
|
-
RETURNING *`,
|
|
453
|
+
RETURNING *`,
|
|
454
|
+
values: columns.map(([, value]) => value),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
async create(input, storageScope) {
|
|
458
|
+
this.rejectStorageScope(storageScope);
|
|
459
|
+
const { sql, values } = this.buildInsert(input);
|
|
460
|
+
const result = await this.query(sql, values);
|
|
454
461
|
if (result.rows[0])
|
|
455
462
|
return this.normalize(result.rows[0]);
|
|
456
463
|
return (await this.findByIdempotencyKey(String(input.idempotency_key)));
|
|
457
464
|
}
|
|
465
|
+
/**
|
|
466
|
+
* Create the Task and run its audit writer in one transaction. A throwing
|
|
467
|
+
* writer rolls back the Task insert; the caller maps the audit error.
|
|
468
|
+
*/
|
|
469
|
+
async createWithAudit(input, runAudit, storageScope) {
|
|
470
|
+
this.rejectStorageScope(storageScope);
|
|
471
|
+
if (typeof this.pool.connect !== 'function') {
|
|
472
|
+
throw this.unavailable(new Error('PostgreSQL transaction client is unavailable'));
|
|
473
|
+
}
|
|
474
|
+
const { sql, values } = this.buildInsert(input);
|
|
475
|
+
const client = await this.pool.connect();
|
|
476
|
+
try {
|
|
477
|
+
await client.query('BEGIN');
|
|
478
|
+
const result = await client.query(sql, values);
|
|
479
|
+
const row = result.rows[0] ?? (await client.query('SELECT * FROM canonical_tasks WHERE idempotency_key = $1 LIMIT 1', [input.idempotency_key])).rows[0];
|
|
480
|
+
if (!row)
|
|
481
|
+
throw storeError('task_store_unavailable', 'PostgreSQL Task store is unavailable', 503);
|
|
482
|
+
const created = this.normalize(row);
|
|
483
|
+
await runAudit(created, client);
|
|
484
|
+
await client.query('COMMIT');
|
|
485
|
+
return created;
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
try {
|
|
489
|
+
await client.query('ROLLBACK');
|
|
490
|
+
}
|
|
491
|
+
catch { /* preserve transaction error */ }
|
|
492
|
+
throw error;
|
|
493
|
+
}
|
|
494
|
+
finally {
|
|
495
|
+
client.release?.();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
458
498
|
async update(taskId, patch, storageScope) {
|
|
459
499
|
this.rejectStorageScope(storageScope);
|
|
460
500
|
// The public kernel marks an applied mutation with `_last_operation_key`
|
|
@@ -123,6 +123,11 @@ export interface CanonicalTaskAuditEntry extends JsonRecord {
|
|
|
123
123
|
changes: JsonRecord;
|
|
124
124
|
source_refs: unknown[];
|
|
125
125
|
}
|
|
126
|
+
export interface CanonicalTaskTransactionClient {
|
|
127
|
+
query(text: string, values?: unknown[]): Promise<{
|
|
128
|
+
rows: Array<Record<string, unknown>>;
|
|
129
|
+
}>;
|
|
130
|
+
}
|
|
126
131
|
export interface CanonicalTaskRepository {
|
|
127
132
|
/** Durable PostgreSQL repositories require a release readiness binding. */
|
|
128
133
|
requiresReadiness?: boolean;
|
|
@@ -131,11 +136,20 @@ export interface CanonicalTaskRepository {
|
|
|
131
136
|
get(taskId: string, storageScope?: string): Promise<CanonicalTaskRecord | null>;
|
|
132
137
|
findByIdempotencyKey(key: string, storageScope?: string): Promise<CanonicalTaskRecord | null>;
|
|
133
138
|
create(input: JsonRecord, storageScope?: string): Promise<CanonicalTaskRecord>;
|
|
139
|
+
/**
|
|
140
|
+
* Create a Task and run the audit writer inside one database transaction.
|
|
141
|
+
* A throwing writer must roll back the Task insert.
|
|
142
|
+
*/
|
|
143
|
+
createWithAudit?(input: JsonRecord, runAudit: (created: CanonicalTaskRecord, client: CanonicalTaskTransactionClient) => Promise<void>, storageScope?: string): Promise<CanonicalTaskRecord>;
|
|
134
144
|
update(taskId: string, patch: JsonRecord, storageScope?: string): Promise<CanonicalTaskRecord>;
|
|
135
145
|
delete(taskId: string, expectedVersion: number, storageScope?: string): Promise<void>;
|
|
136
146
|
}
|
|
137
147
|
export interface CanonicalTaskAuditRepository {
|
|
138
|
-
|
|
148
|
+
/** Declare support before offering the same-transaction audit contract. */
|
|
149
|
+
supportsTransactionClient?: boolean;
|
|
150
|
+
upsertAuditLog(entry: CanonicalTaskAuditEntry, options?: {
|
|
151
|
+
client?: CanonicalTaskTransactionClient;
|
|
152
|
+
}): Promise<void>;
|
|
139
153
|
}
|
|
140
154
|
export interface CanonicalTaskAuthorizationRequest {
|
|
141
155
|
action: CanonicalTaskAction;
|
|
@@ -283,6 +297,7 @@ export declare class CanonicalTaskService {
|
|
|
283
297
|
private requireExpectedVersion;
|
|
284
298
|
private assertVersion;
|
|
285
299
|
private executeOperation;
|
|
300
|
+
private buildAuditEntry;
|
|
286
301
|
private audit;
|
|
287
302
|
private read;
|
|
288
303
|
private normalizeResponse;
|
|
@@ -476,18 +476,29 @@ export class CanonicalTaskService {
|
|
|
476
476
|
await this.authorize('read', normalizedContext, existing);
|
|
477
477
|
return { recovered: true, result: this.normalizeResponse(existing) };
|
|
478
478
|
};
|
|
479
|
+
const transactionalAudit = typeof this.repository.createWithAudit === 'function'
|
|
480
|
+
&& this.auditRepository?.supportsTransactionClient === true;
|
|
481
|
+
let auditedInTransaction = false;
|
|
479
482
|
const run = async () => {
|
|
480
483
|
const recovered = await recover();
|
|
481
484
|
if (recovered.recovered && recovered.result)
|
|
482
485
|
return recovered.result;
|
|
483
486
|
await this.assertMutationReady();
|
|
484
|
-
const
|
|
487
|
+
const input = {
|
|
485
488
|
...payload,
|
|
486
489
|
status: 'pending',
|
|
487
490
|
version: 1,
|
|
488
491
|
idempotency_key: operation,
|
|
489
492
|
payload_fingerprint: inputFingerprint,
|
|
490
|
-
}
|
|
493
|
+
};
|
|
494
|
+
if (transactionalAudit) {
|
|
495
|
+
return this.read(() => this.repository.createWithAudit(input, async (created, client) => {
|
|
496
|
+
const normalizedCreated = this.normalizeResponse(created);
|
|
497
|
+
await this.audit(normalizedContext, 'canonical_task.created', normalizedCreated, { before: null, after: normalizedCreated }, operation, inputFingerprint, client);
|
|
498
|
+
auditedInTransaction = true;
|
|
499
|
+
}, ...this.scopeArgs(normalizedContext)));
|
|
500
|
+
}
|
|
501
|
+
const created = await this.read(() => this.repository.create(input, ...this.scopeArgs(normalizedContext)));
|
|
491
502
|
return this.normalizeResponse(created);
|
|
492
503
|
};
|
|
493
504
|
const result = await this.executeOperation({
|
|
@@ -501,10 +512,12 @@ export class CanonicalTaskService {
|
|
|
501
512
|
if (normalizedContext.storageScope !== undefined)
|
|
502
513
|
await this.authorize('read', normalizedContext, result);
|
|
503
514
|
const normalized = this.normalizeResponse(result);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
515
|
+
if (!auditedInTransaction) {
|
|
516
|
+
// Audit each invocation, including a replay/recovery. The deterministic
|
|
517
|
+
// entry id lets an upsert sink collapse retries without losing evidence
|
|
518
|
+
// that the audit write was attempted again.
|
|
519
|
+
await this.audit(normalizedContext, 'canonical_task.created', normalized, { before: null, after: normalized }, operation, inputFingerprint);
|
|
520
|
+
}
|
|
508
521
|
return normalized;
|
|
509
522
|
}
|
|
510
523
|
async updateTask(taskId, input, expectedVersionOrContext, maybeContext) {
|
|
@@ -1054,9 +1067,9 @@ export class CanonicalTaskService {
|
|
|
1054
1067
|
? this.operationRepository.execute(request)
|
|
1055
1068
|
: request.run();
|
|
1056
1069
|
}
|
|
1057
|
-
|
|
1070
|
+
buildAuditEntry(context, action, task, changes, operationKey, operationFingerprint) {
|
|
1058
1071
|
if (!this.auditRepository)
|
|
1059
|
-
return;
|
|
1072
|
+
return null;
|
|
1060
1073
|
const actor = context.auditPrincipal
|
|
1061
1074
|
? normalizeCanonicalTaskPrincipal(context.auditPrincipal)
|
|
1062
1075
|
: context.principal;
|
|
@@ -1078,8 +1091,14 @@ export class CanonicalTaskService {
|
|
|
1078
1091
|
changes,
|
|
1079
1092
|
source_refs: Array.isArray(task.source_refs) ? task.source_refs : [],
|
|
1080
1093
|
};
|
|
1094
|
+
return entry;
|
|
1095
|
+
}
|
|
1096
|
+
async audit(context, action, task, changes, operationKey, operationFingerprint, client) {
|
|
1097
|
+
const entry = this.buildAuditEntry(context, action, task, changes, operationKey, operationFingerprint);
|
|
1098
|
+
if (!entry)
|
|
1099
|
+
return;
|
|
1081
1100
|
try {
|
|
1082
|
-
await this.auditRepository.upsertAuditLog(entry);
|
|
1101
|
+
await this.auditRepository.upsertAuditLog(entry, client ? { client } : undefined);
|
|
1083
1102
|
}
|
|
1084
1103
|
catch {
|
|
1085
1104
|
throw new CanonicalTaskError('task_audit_unavailable', 'Task audit is unavailable', 503);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type MeetingSourceProvider } from './meeting-source-sync.js';
|
|
2
|
+
/**
|
|
3
|
+
* Provider adapters only translate provider-specific MCP responses into the
|
|
4
|
+
* provider-neutral artifact shape consumed by the sync core. Transport,
|
|
5
|
+
* OAuth, credentials, filesystem paths, and persistence stay with the host
|
|
6
|
+
* and are supplied through this client port.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MEETING_SOURCE_MCP_ADAPTER_VERSION: "brainbase.meeting-source-mcp-adapters.v1";
|
|
9
|
+
type UnknownRecord = Record<string, unknown>;
|
|
10
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
11
|
+
export type MeetingSourceMcpCallTool = (name: string, args: Record<string, unknown>) => MaybePromise<unknown>;
|
|
12
|
+
export interface MeetingSourceMcpClient {
|
|
13
|
+
readonly callTool: MeetingSourceMcpCallTool;
|
|
14
|
+
readonly listTools?: () => MaybePromise<unknown>;
|
|
15
|
+
readonly listResources?: () => MaybePromise<unknown>;
|
|
16
|
+
}
|
|
17
|
+
export interface MeetingSourceMcpAdapterConfig {
|
|
18
|
+
readonly tool?: string;
|
|
19
|
+
readonly poll_tool?: string;
|
|
20
|
+
readonly list_tool?: string;
|
|
21
|
+
readonly detail_tool?: string;
|
|
22
|
+
readonly transcript_tool?: string;
|
|
23
|
+
readonly note_tool?: string;
|
|
24
|
+
readonly provider_poll?: boolean;
|
|
25
|
+
readonly limit?: number;
|
|
26
|
+
readonly page_size?: number;
|
|
27
|
+
readonly arguments?: Readonly<Record<string, unknown>>;
|
|
28
|
+
readonly resource_prefix?: string;
|
|
29
|
+
readonly resourcePrefix?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface MeetingSourceMcpAdapterOptions {
|
|
32
|
+
readonly provider: MeetingSourceProvider;
|
|
33
|
+
readonly client: MeetingSourceMcpClient;
|
|
34
|
+
readonly config?: MeetingSourceMcpAdapterConfig;
|
|
35
|
+
readonly log?: {
|
|
36
|
+
readonly warn?: (...args: unknown[]) => void;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface MeetingSourceMcpPollInput {
|
|
40
|
+
/** The provider state is accepted for Host compatibility and is not persisted here. */
|
|
41
|
+
readonly provider?: UnknownRecord;
|
|
42
|
+
readonly cursor?: UnknownRecord;
|
|
43
|
+
readonly since?: string | null;
|
|
44
|
+
readonly until?: string | null;
|
|
45
|
+
}
|
|
46
|
+
export interface MeetingSourceMcpTestResult {
|
|
47
|
+
readonly ok: true;
|
|
48
|
+
readonly auth_status: 'connected';
|
|
49
|
+
readonly capabilities: readonly string[];
|
|
50
|
+
readonly configured_tool: string | null;
|
|
51
|
+
}
|
|
52
|
+
export type MeetingSourceMcpArtifact = UnknownRecord;
|
|
53
|
+
/** Extracts an array from the common MCP tool response envelopes. */
|
|
54
|
+
export declare function extractMeetingSourceArtifactList(payload: unknown): MeetingSourceMcpArtifact[];
|
|
55
|
+
/** Returns the structured payload or the textual payload from a CallToolResult. */
|
|
56
|
+
export declare function extractMeetingSourceToolPayload(payload: unknown): unknown;
|
|
57
|
+
/** Reads transcript/note text from provider-specific nested envelopes. */
|
|
58
|
+
export declare function extractMeetingSourceToolText(value: unknown): string;
|
|
59
|
+
export declare function meetingSourceArtifactInWindow(value: unknown, window?: {
|
|
60
|
+
readonly since?: string | null;
|
|
61
|
+
readonly until?: string | null;
|
|
62
|
+
}): boolean;
|
|
63
|
+
export declare class MeetingSourceMcpAdapter {
|
|
64
|
+
readonly provider: MeetingSourceProvider;
|
|
65
|
+
readonly client: MeetingSourceMcpClient;
|
|
66
|
+
readonly config: MeetingSourceMcpAdapterConfig;
|
|
67
|
+
readonly log: MeetingSourceMcpAdapterOptions['log'];
|
|
68
|
+
constructor(options: MeetingSourceMcpAdapterOptions);
|
|
69
|
+
private toolArgs;
|
|
70
|
+
private pollTactiq;
|
|
71
|
+
private pollPlaud;
|
|
72
|
+
test(_input?: unknown): Promise<MeetingSourceMcpTestResult>;
|
|
73
|
+
poll(input?: MeetingSourceMcpPollInput): Promise<MeetingSourceMcpArtifact[]>;
|
|
74
|
+
}
|
|
75
|
+
export declare function createMeetingSourceMcpAdapter(options: MeetingSourceMcpAdapterOptions): MeetingSourceMcpAdapter;
|
|
76
|
+
export {};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { SUPPORTED_MEETING_SOURCE_PROVIDERS } from './meeting-source-sync.js';
|
|
2
|
+
/**
|
|
3
|
+
* Provider adapters only translate provider-specific MCP responses into the
|
|
4
|
+
* provider-neutral artifact shape consumed by the sync core. Transport,
|
|
5
|
+
* OAuth, credentials, filesystem paths, and persistence stay with the host
|
|
6
|
+
* and are supplied through this client port.
|
|
7
|
+
*/
|
|
8
|
+
export const MEETING_SOURCE_MCP_ADAPTER_VERSION = 'brainbase.meeting-source-mcp-adapters.v1';
|
|
9
|
+
function asRecord(value) {
|
|
10
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
11
|
+
}
|
|
12
|
+
function parseJsonMaybe(value) {
|
|
13
|
+
if (typeof value !== 'string')
|
|
14
|
+
return value;
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
if (!trimmed)
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(trimmed);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Extracts an array from the common MCP tool response envelopes. */
|
|
26
|
+
export function extractMeetingSourceArtifactList(payload) {
|
|
27
|
+
if (!payload)
|
|
28
|
+
return [];
|
|
29
|
+
if (Array.isArray(payload))
|
|
30
|
+
return payload.filter((item) => Boolean(asRecord(item)));
|
|
31
|
+
const record = asRecord(payload);
|
|
32
|
+
if (!record) {
|
|
33
|
+
const parsed = parseJsonMaybe(payload);
|
|
34
|
+
return parsed === payload ? [] : extractMeetingSourceArtifactList(parsed);
|
|
35
|
+
}
|
|
36
|
+
for (const key of ['artifacts', 'items', 'records', 'results', 'files', 'data', 'transcripts', 'recordings']) {
|
|
37
|
+
const value = record[key];
|
|
38
|
+
if (Array.isArray(value))
|
|
39
|
+
return value.filter((item) => Boolean(asRecord(item)));
|
|
40
|
+
}
|
|
41
|
+
if (record.structuredContent !== undefined)
|
|
42
|
+
return extractMeetingSourceArtifactList(record.structuredContent);
|
|
43
|
+
if (Array.isArray(record.content)) {
|
|
44
|
+
const parsedItems = record.content.flatMap((item) => {
|
|
45
|
+
const entry = asRecord(item);
|
|
46
|
+
if (!entry || entry.type !== 'text' || typeof entry.text !== 'string')
|
|
47
|
+
return [];
|
|
48
|
+
const parsed = parseJsonMaybe(entry.text);
|
|
49
|
+
return parsed === null ? [] : extractMeetingSourceArtifactList(parsed);
|
|
50
|
+
});
|
|
51
|
+
if (parsedItems.length)
|
|
52
|
+
return parsedItems;
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
/** Returns the structured payload or the textual payload from a CallToolResult. */
|
|
57
|
+
export function extractMeetingSourceToolPayload(payload) {
|
|
58
|
+
if (!payload)
|
|
59
|
+
return null;
|
|
60
|
+
const record = asRecord(payload);
|
|
61
|
+
if (!record)
|
|
62
|
+
return parseJsonMaybe(payload);
|
|
63
|
+
if (record.structuredContent !== undefined)
|
|
64
|
+
return record.structuredContent;
|
|
65
|
+
if (Array.isArray(record.content)) {
|
|
66
|
+
const textItems = record.content
|
|
67
|
+
.map((item) => asRecord(item))
|
|
68
|
+
.filter((item) => Boolean(item && item.type === 'text' && typeof item.text === 'string'));
|
|
69
|
+
for (const item of textItems) {
|
|
70
|
+
const parsed = parseJsonMaybe(item.text);
|
|
71
|
+
if (parsed !== null)
|
|
72
|
+
return parsed;
|
|
73
|
+
}
|
|
74
|
+
return textItems.map((item) => item.text).join('\n');
|
|
75
|
+
}
|
|
76
|
+
return payload;
|
|
77
|
+
}
|
|
78
|
+
/** Reads transcript/note text from provider-specific nested envelopes. */
|
|
79
|
+
export function extractMeetingSourceToolText(value) {
|
|
80
|
+
if (!value)
|
|
81
|
+
return '';
|
|
82
|
+
if (typeof value === 'string')
|
|
83
|
+
return value;
|
|
84
|
+
if (Array.isArray(value))
|
|
85
|
+
return value.map(extractMeetingSourceToolText).filter(Boolean).join('\n');
|
|
86
|
+
const record = asRecord(value);
|
|
87
|
+
if (!record)
|
|
88
|
+
return '';
|
|
89
|
+
for (const key of ['text', 'transcript', 'transcript_text', 'data_content', 'content', 'markdown', 'summary']) {
|
|
90
|
+
if (typeof record[key] === 'string')
|
|
91
|
+
return record[key];
|
|
92
|
+
}
|
|
93
|
+
return extractMeetingSourceToolText(record.detailedSummary ?? record.data);
|
|
94
|
+
}
|
|
95
|
+
export function meetingSourceArtifactInWindow(value, window = {}) {
|
|
96
|
+
if (!value)
|
|
97
|
+
return true;
|
|
98
|
+
const timestamp = Date.parse(String(value));
|
|
99
|
+
if (Number.isNaN(timestamp))
|
|
100
|
+
return true;
|
|
101
|
+
if (window.since && timestamp < Date.parse(window.since))
|
|
102
|
+
return false;
|
|
103
|
+
if (window.until && timestamp > Date.parse(window.until))
|
|
104
|
+
return false;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
function extractResourceList(payload) {
|
|
108
|
+
const record = asRecord(payload);
|
|
109
|
+
if (Array.isArray(payload))
|
|
110
|
+
return payload.filter((item) => Boolean(asRecord(item)));
|
|
111
|
+
if (!record)
|
|
112
|
+
return [];
|
|
113
|
+
if (Array.isArray(record.resources))
|
|
114
|
+
return record.resources.filter((item) => Boolean(asRecord(item)));
|
|
115
|
+
if (record.structuredContent !== undefined)
|
|
116
|
+
return extractResourceList(record.structuredContent);
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
function configuredTool(config) {
|
|
120
|
+
return config.tool ?? config.poll_tool ?? config.list_tool ?? null;
|
|
121
|
+
}
|
|
122
|
+
function boundedNumber(value, fallback, maximum) {
|
|
123
|
+
const parsed = Number(value);
|
|
124
|
+
if (!Number.isFinite(parsed))
|
|
125
|
+
return fallback;
|
|
126
|
+
return Math.min(Math.max(Math.trunc(parsed), 1), maximum);
|
|
127
|
+
}
|
|
128
|
+
function stringValue(value) {
|
|
129
|
+
return typeof value === 'string' && value.trim() ? value : null;
|
|
130
|
+
}
|
|
131
|
+
function logFailure(logger, provider, kind, externalId) {
|
|
132
|
+
// Do not include provider response/error text: it can contain an access token.
|
|
133
|
+
logger?.warn?.(`[meeting-source] ${provider} ${kind} fetch failed for ${externalId}`);
|
|
134
|
+
}
|
|
135
|
+
function toolNames(payload) {
|
|
136
|
+
const record = asRecord(payload);
|
|
137
|
+
const tools = record && Array.isArray(record.tools) ? record.tools : [];
|
|
138
|
+
return tools
|
|
139
|
+
.map((tool) => asRecord(tool)?.name)
|
|
140
|
+
.filter((name) => typeof name === 'string' && name.trim().length > 0);
|
|
141
|
+
}
|
|
142
|
+
export class MeetingSourceMcpAdapter {
|
|
143
|
+
provider;
|
|
144
|
+
client;
|
|
145
|
+
config;
|
|
146
|
+
log;
|
|
147
|
+
constructor(options) {
|
|
148
|
+
if (!options?.client || typeof options.client.callTool !== 'function') {
|
|
149
|
+
throw new TypeError('client.callTool is required');
|
|
150
|
+
}
|
|
151
|
+
if (!SUPPORTED_MEETING_SOURCE_PROVIDERS.includes(options.provider)) {
|
|
152
|
+
throw new Error(`unsupported meeting source provider adapter: ${String(options.provider)}`);
|
|
153
|
+
}
|
|
154
|
+
this.provider = options.provider;
|
|
155
|
+
this.client = options.client;
|
|
156
|
+
this.config = Object.freeze({ ...(options.config ?? {}) });
|
|
157
|
+
this.log = options.log;
|
|
158
|
+
}
|
|
159
|
+
toolArgs(input) {
|
|
160
|
+
return {
|
|
161
|
+
...(this.config.arguments ?? {}),
|
|
162
|
+
since: input.since ?? null,
|
|
163
|
+
until: input.until ?? null,
|
|
164
|
+
updated_since: input.since ?? null,
|
|
165
|
+
cursor: input.cursor ?? {},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async pollTactiq(input) {
|
|
169
|
+
const result = await this.client.callTool(this.config.tool ?? this.config.poll_tool ?? 'list_recent_meetings', {
|
|
170
|
+
...(this.config.arguments ?? {}),
|
|
171
|
+
limit: boundedNumber(this.config.limit ?? this.config.page_size, 50, 50),
|
|
172
|
+
});
|
|
173
|
+
const meetings = extractMeetingSourceArtifactList(result).filter((meeting) => meetingSourceArtifactInWindow(meeting.createdAt ?? meeting.created_at ?? meeting.started_at, input));
|
|
174
|
+
const artifacts = [];
|
|
175
|
+
for (const meeting of meetings) {
|
|
176
|
+
const meetingId = stringValue(meeting.id ?? meeting.external_id);
|
|
177
|
+
let detail = null;
|
|
178
|
+
if (meetingId) {
|
|
179
|
+
try {
|
|
180
|
+
detail = asRecord(extractMeetingSourceToolPayload(await this.client.callTool(this.config.detail_tool ?? 'get_meeting', { id: meetingId })));
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
logFailure(this.log, this.provider, 'detail', meetingId);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const summary = asRecord(detail?.detailedSummary ?? meeting.detailedSummary);
|
|
187
|
+
const noteText = summary?.status === 'ready'
|
|
188
|
+
? extractMeetingSourceToolText(summary.content)
|
|
189
|
+
: extractMeetingSourceToolText(summary);
|
|
190
|
+
artifacts.push({
|
|
191
|
+
...meeting,
|
|
192
|
+
...(detail ?? {}),
|
|
193
|
+
...(meetingId ? { external_id: meetingId } : {}),
|
|
194
|
+
title: detail?.title ?? meeting.title,
|
|
195
|
+
started_at: meeting.createdAt ?? detail?.createdAt ?? meeting.created_at ?? detail?.created_at ?? null,
|
|
196
|
+
updated_at: meeting.createdAt ?? detail?.createdAt ?? meeting.updated_at ?? detail?.updated_at ?? null,
|
|
197
|
+
participants: meeting.attendees ?? detail?.attendees ?? meeting.participants ?? [],
|
|
198
|
+
note_text: noteText,
|
|
199
|
+
meeting_mode: 'online',
|
|
200
|
+
resource_uri: meeting.url ?? detail?.url ?? meeting.resource_uri ?? null,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return artifacts;
|
|
204
|
+
}
|
|
205
|
+
async pollPlaud(input) {
|
|
206
|
+
const result = await this.client.callTool(this.config.tool ?? this.config.poll_tool ?? 'list_files', {
|
|
207
|
+
page: 1,
|
|
208
|
+
page_size: boundedNumber(this.config.page_size, 20, 200),
|
|
209
|
+
...(input.since ? { date_from: input.since.slice(0, 10) } : {}),
|
|
210
|
+
...(input.until ? { date_to: input.until.slice(0, 10) } : {}),
|
|
211
|
+
...(this.config.arguments ?? {}),
|
|
212
|
+
});
|
|
213
|
+
const files = extractMeetingSourceArtifactList(result).filter((file) => meetingSourceArtifactInWindow(file.start_at ?? file.created_at ?? file.started_at, input));
|
|
214
|
+
const artifacts = [];
|
|
215
|
+
for (const file of files) {
|
|
216
|
+
const fileId = stringValue(file.id ?? file.file_id ?? file.fileId);
|
|
217
|
+
let transcriptText = '';
|
|
218
|
+
let noteText = '';
|
|
219
|
+
if (fileId) {
|
|
220
|
+
try {
|
|
221
|
+
transcriptText = extractMeetingSourceToolText(extractMeetingSourceToolPayload(await this.client.callTool(this.config.transcript_tool ?? 'get_transcript', { file_id: fileId })));
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
logFailure(this.log, this.provider, 'transcript', fileId);
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
noteText = extractMeetingSourceToolText(extractMeetingSourceToolPayload(await this.client.callTool(this.config.note_tool ?? 'get_note', { file_id: fileId })));
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
logFailure(this.log, this.provider, 'note', fileId);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
artifacts.push({
|
|
234
|
+
...file,
|
|
235
|
+
...(fileId ? { external_id: fileId } : {}),
|
|
236
|
+
title: file.name ?? file.title,
|
|
237
|
+
started_at: file.start_at ?? file.created_at ?? null,
|
|
238
|
+
updated_at: file.created_at ?? file.start_at ?? null,
|
|
239
|
+
transcript_text: transcriptText,
|
|
240
|
+
note_text: noteText,
|
|
241
|
+
meeting_mode: 'offline',
|
|
242
|
+
resource_uri: fileId ? `plaud:${fileId}` : null,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return artifacts;
|
|
246
|
+
}
|
|
247
|
+
async test(_input) {
|
|
248
|
+
let capabilities = [];
|
|
249
|
+
if (typeof this.client.listTools === 'function') {
|
|
250
|
+
try {
|
|
251
|
+
capabilities = toolNames(await this.client.listTools());
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
capabilities = [];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
ok: true,
|
|
259
|
+
auth_status: 'connected',
|
|
260
|
+
capabilities,
|
|
261
|
+
configured_tool: configuredTool(this.config),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async poll(input = {}) {
|
|
265
|
+
// Preserve the legacy Host predicate: provider polling is selected when it
|
|
266
|
+
// is explicitly enabled or when no primary tool is configured. The legacy
|
|
267
|
+
// provider path intentionally treats poll_tool/list_tool as provider
|
|
268
|
+
// configuration and only uses them inside the provider-specific adapter.
|
|
269
|
+
const providerPoll = this.config.provider_poll === true || !this.config.tool;
|
|
270
|
+
if (this.provider === 'tactiq' && providerPoll)
|
|
271
|
+
return this.pollTactiq(input);
|
|
272
|
+
if (this.provider === 'plaud' && providerPoll)
|
|
273
|
+
return this.pollPlaud(input);
|
|
274
|
+
const tool = configuredTool(this.config);
|
|
275
|
+
if (tool) {
|
|
276
|
+
return extractMeetingSourceArtifactList(await this.client.callTool(tool, this.toolArgs(input)));
|
|
277
|
+
}
|
|
278
|
+
if (typeof this.client.listResources !== 'function')
|
|
279
|
+
return [];
|
|
280
|
+
const prefix = this.config.resource_prefix ?? this.config.resourcePrefix;
|
|
281
|
+
return extractResourceList(await this.client.listResources())
|
|
282
|
+
.filter((resource) => !prefix || String(resource.uri ?? '').startsWith(prefix))
|
|
283
|
+
.map((resource) => ({
|
|
284
|
+
id: resource.uri,
|
|
285
|
+
title: resource.name ?? resource.title ?? resource.uri,
|
|
286
|
+
resource_uri: resource.uri,
|
|
287
|
+
updated_at: resource.updated_at ?? resource.modified_at ?? null,
|
|
288
|
+
provider: this.provider,
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
export function createMeetingSourceMcpAdapter(options) {
|
|
293
|
+
return new MeetingSourceMcpAdapter(options);
|
|
294
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { dedupeMeetingSourceArtifacts, normalizeMeetingSourceArtifact, runMeetingSourceSync, type MeetingSourceArtifact, type MeetingSourceProvider, type MeetingSourceSyncPorts, type MeetingSourceSyncResult } from './meeting-source-sync.js';
|
|
2
|
+
/**
|
|
3
|
+
* Host composition seam for the already implemented provider-neutral sync
|
|
4
|
+
* core. It deliberately delegates every operation; normalization, dedupe,
|
|
5
|
+
* durable claim/save/readback, and retry behavior remain owned by the core.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MEETING_SOURCE_SYNC_COMMON_ADAPTER_VERSION: "brainbase.meeting-source-sync-common-adapter.v1";
|
|
8
|
+
export interface MeetingSourceSyncCommonCore {
|
|
9
|
+
readonly normalizeMeetingSourceArtifact: typeof normalizeMeetingSourceArtifact;
|
|
10
|
+
readonly dedupeMeetingSourceArtifacts: typeof dedupeMeetingSourceArtifacts;
|
|
11
|
+
readonly runMeetingSourceSync: typeof runMeetingSourceSync;
|
|
12
|
+
}
|
|
13
|
+
export interface MeetingSourceSyncCommonAdapter {
|
|
14
|
+
normalizeSourceArtifact(raw: Record<string, unknown>, provider: MeetingSourceProvider): MeetingSourceArtifact;
|
|
15
|
+
dedupeSourceArtifacts(artifacts: readonly MeetingSourceArtifact[]): ReturnType<typeof dedupeMeetingSourceArtifacts>;
|
|
16
|
+
sync(input: Parameters<typeof runMeetingSourceSync>[0], ports: MeetingSourceSyncPorts): Promise<MeetingSourceSyncResult>;
|
|
17
|
+
}
|
|
18
|
+
export declare function createMeetingSourceSyncCommonAdapter(commonCore: MeetingSourceSyncCommonCore): MeetingSourceSyncCommonAdapter;
|
|
19
|
+
export declare const createDefaultMeetingSourceSyncCommonAdapter: () => MeetingSourceSyncCommonAdapter;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { dedupeMeetingSourceArtifacts, normalizeMeetingSourceArtifact, runMeetingSourceSync, } from './meeting-source-sync.js';
|
|
2
|
+
/**
|
|
3
|
+
* Host composition seam for the already implemented provider-neutral sync
|
|
4
|
+
* core. It deliberately delegates every operation; normalization, dedupe,
|
|
5
|
+
* durable claim/save/readback, and retry behavior remain owned by the core.
|
|
6
|
+
*/
|
|
7
|
+
export const MEETING_SOURCE_SYNC_COMMON_ADAPTER_VERSION = 'brainbase.meeting-source-sync-common-adapter.v1';
|
|
8
|
+
export function createMeetingSourceSyncCommonAdapter(commonCore) {
|
|
9
|
+
if (!commonCore || typeof commonCore.runMeetingSourceSync !== 'function') {
|
|
10
|
+
throw new TypeError('meeting source sync common core is required');
|
|
11
|
+
}
|
|
12
|
+
if (typeof commonCore.normalizeMeetingSourceArtifact !== 'function') {
|
|
13
|
+
throw new TypeError('meeting source sync normalization export is required');
|
|
14
|
+
}
|
|
15
|
+
if (typeof commonCore.dedupeMeetingSourceArtifacts !== 'function') {
|
|
16
|
+
throw new TypeError('meeting source sync dedupe export is required');
|
|
17
|
+
}
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
normalizeSourceArtifact(raw, provider) {
|
|
20
|
+
return commonCore.normalizeMeetingSourceArtifact(raw, provider);
|
|
21
|
+
},
|
|
22
|
+
dedupeSourceArtifacts(artifacts) {
|
|
23
|
+
return commonCore.dedupeMeetingSourceArtifacts(artifacts);
|
|
24
|
+
},
|
|
25
|
+
sync(input, ports) {
|
|
26
|
+
return commonCore.runMeetingSourceSync(input, ports);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export const createDefaultMeetingSourceSyncCommonAdapter = () => createMeetingSourceSyncCommonAdapter({
|
|
31
|
+
normalizeMeetingSourceArtifact,
|
|
32
|
+
dedupeMeetingSourceArtifacts,
|
|
33
|
+
runMeetingSourceSync,
|
|
34
|
+
});
|
package/dist/mesh.d.ts
CHANGED
|
@@ -28,6 +28,16 @@ export declare function createMeshEnvelope(input: MeshEnvelopeInput, options?: {
|
|
|
28
28
|
nonceFactory?: () => string;
|
|
29
29
|
}): MeshEnvelope;
|
|
30
30
|
export declare function normalizeMeshEnvelope(value: unknown): MeshEnvelope;
|
|
31
|
+
/**
|
|
32
|
+
* Parse a serialized or already-decoded envelope through the same strict
|
|
33
|
+
* boundary used by the host adapter.
|
|
34
|
+
*
|
|
35
|
+
* The host runtime accepts both JSON strings and objects. Keeping that input
|
|
36
|
+
* shape here lets a host compare the local contract result before handing an
|
|
37
|
+
* envelope to its network or crypto layer without moving either dependency
|
|
38
|
+
* into the portable package.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseMeshEnvelope(value: unknown): MeshEnvelope;
|
|
31
41
|
export interface MeshProjectPort {
|
|
32
42
|
projectId: string;
|
|
33
43
|
localPath?: string;
|
package/dist/mesh.js
CHANGED
|
@@ -21,16 +21,24 @@ function text(value, field) {
|
|
|
21
21
|
throw new MeshContractError(field);
|
|
22
22
|
return value.trim();
|
|
23
23
|
}
|
|
24
|
-
function isJson(value) {
|
|
25
|
-
if (value === null || ['string', '
|
|
24
|
+
function isJson(value, ancestors = new Set()) {
|
|
25
|
+
if (value === null || ['string', 'boolean'].includes(typeof value))
|
|
26
26
|
return true;
|
|
27
|
-
if (
|
|
28
|
-
return
|
|
29
|
-
if (typeof value
|
|
30
|
-
return
|
|
31
|
-
|
|
27
|
+
if (typeof value === 'number')
|
|
28
|
+
return Number.isFinite(value);
|
|
29
|
+
if (typeof value !== 'object' || ancestors.has(value))
|
|
30
|
+
return false;
|
|
31
|
+
ancestors.add(value);
|
|
32
|
+
const valid = Array.isArray(value)
|
|
33
|
+
? value.every((entry) => isJson(entry, ancestors))
|
|
34
|
+
: (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)
|
|
35
|
+
&& Object.values(value).every((entry) => isJson(entry, ancestors));
|
|
36
|
+
ancestors.delete(value);
|
|
37
|
+
return valid;
|
|
32
38
|
}
|
|
33
39
|
export function createMeshEnvelope(input, options = {}) {
|
|
40
|
+
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
41
|
+
throw new MeshContractError('envelope');
|
|
34
42
|
const from = text(input.from, 'from');
|
|
35
43
|
const to = text(input.to, 'to');
|
|
36
44
|
if (!MESH_ENVELOPE_TYPES.includes(input.type))
|
|
@@ -38,16 +46,16 @@ export function createMeshEnvelope(input, options = {}) {
|
|
|
38
46
|
if (input.payload !== undefined && !isJson(input.payload))
|
|
39
47
|
throw new MeshContractError('payload');
|
|
40
48
|
const ts = options.now ?? Date.now();
|
|
41
|
-
if (!Number.isFinite(ts))
|
|
49
|
+
if (!Number.isFinite(ts) || ts <= 0)
|
|
42
50
|
throw new MeshContractError('ts');
|
|
43
51
|
return {
|
|
44
|
-
id: options.idFactory?.() ?? randomUUID(),
|
|
52
|
+
id: text(options.idFactory?.() ?? randomUUID(), 'id'),
|
|
45
53
|
from,
|
|
46
54
|
to,
|
|
47
55
|
type: input.type,
|
|
48
56
|
payload: input.payload ?? null,
|
|
49
57
|
ts,
|
|
50
|
-
nonce: options.nonceFactory?.() ?? randomUUID()
|
|
58
|
+
nonce: text(options.nonceFactory?.() ?? randomUUID(), 'nonce')
|
|
51
59
|
};
|
|
52
60
|
}
|
|
53
61
|
export function normalizeMeshEnvelope(value) {
|
|
@@ -64,18 +72,40 @@ export function normalizeMeshEnvelope(value) {
|
|
|
64
72
|
if (!Number.isFinite(candidate.ts) || typeof candidate.ts !== 'number' || candidate.ts <= 0) {
|
|
65
73
|
throw new MeshContractError('ts');
|
|
66
74
|
}
|
|
67
|
-
|
|
75
|
+
const payload = candidate.payload ?? null;
|
|
76
|
+
if (!isJson(payload))
|
|
68
77
|
throw new MeshContractError('payload');
|
|
69
78
|
return {
|
|
70
79
|
id,
|
|
71
80
|
from,
|
|
72
81
|
to,
|
|
73
82
|
type: candidate.type,
|
|
74
|
-
payload
|
|
83
|
+
payload,
|
|
75
84
|
ts: candidate.ts,
|
|
76
85
|
nonce
|
|
77
86
|
};
|
|
78
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Parse a serialized or already-decoded envelope through the same strict
|
|
90
|
+
* boundary used by the host adapter.
|
|
91
|
+
*
|
|
92
|
+
* The host runtime accepts both JSON strings and objects. Keeping that input
|
|
93
|
+
* shape here lets a host compare the local contract result before handing an
|
|
94
|
+
* envelope to its network or crypto layer without moving either dependency
|
|
95
|
+
* into the portable package.
|
|
96
|
+
*/
|
|
97
|
+
export function parseMeshEnvelope(value) {
|
|
98
|
+
let decoded = value;
|
|
99
|
+
if (typeof value === 'string') {
|
|
100
|
+
try {
|
|
101
|
+
decoded = JSON.parse(value);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
throw new MeshContractError('envelope', 'mesh envelope JSON is invalid');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return normalizeMeshEnvelope(decoded);
|
|
108
|
+
}
|
|
79
109
|
export function normalizeMeshNodeProfile(value) {
|
|
80
110
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
81
111
|
throw new MeshContractError('profile');
|
|
@@ -94,7 +124,8 @@ export function normalizeMeshNodeProfile(value) {
|
|
|
94
124
|
const ports = item.ports === undefined
|
|
95
125
|
? undefined
|
|
96
126
|
: item.ports && typeof item.ports === 'object' && !Array.isArray(item.ports)
|
|
97
|
-
&& Object.entries(item.ports).every(([key, port]) => Boolean(key)
|
|
127
|
+
&& Object.entries(item.ports).every(([key, port]) => Boolean(key)
|
|
128
|
+
&& (typeof port === 'string' || (typeof port === 'number' && Number.isFinite(port))))
|
|
98
129
|
? { ...item.ports }
|
|
99
130
|
: (() => { throw new MeshContractError(`projects.${index}.ports`); })();
|
|
100
131
|
return { projectId, localPath, globInclude, ports };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable batch boundary for meeting-derived Personal KG extraction.
|
|
3
|
+
*
|
|
4
|
+
* A consumer supplies the meeting rule/extraction callback because source
|
|
5
|
+
* schemas, identity, redaction policy, and candidate shape are tenant-owned.
|
|
6
|
+
* This module owns the common collection/count contract and keeps the batch
|
|
7
|
+
* boundary independent from any database or model provider.
|
|
8
|
+
*/
|
|
9
|
+
export interface RoutinePersonalKgMeetingResult<TCandidate = unknown> {
|
|
10
|
+
adopted?: readonly TCandidate[];
|
|
11
|
+
rejected?: readonly unknown[];
|
|
12
|
+
needs_review?: readonly unknown[];
|
|
13
|
+
}
|
|
14
|
+
export interface RoutinePersonalKgExtractionOptions<TMeeting, TCandidate = unknown> {
|
|
15
|
+
date: string;
|
|
16
|
+
meetings?: readonly TMeeting[];
|
|
17
|
+
sourceSystem: string;
|
|
18
|
+
extract: (input: {
|
|
19
|
+
meeting: TMeeting;
|
|
20
|
+
date: string;
|
|
21
|
+
index: number;
|
|
22
|
+
}) => RoutinePersonalKgMeetingResult<TCandidate>;
|
|
23
|
+
dedupe?: (candidates: readonly TCandidate[]) => readonly TCandidate[];
|
|
24
|
+
}
|
|
25
|
+
export interface RoutinePersonalKgExtractionResult<TCandidate = unknown> {
|
|
26
|
+
date: string;
|
|
27
|
+
source_system: string;
|
|
28
|
+
counts: {
|
|
29
|
+
adopted: number;
|
|
30
|
+
rejected: number;
|
|
31
|
+
needs_review: number;
|
|
32
|
+
};
|
|
33
|
+
adopted: TCandidate[];
|
|
34
|
+
rejected: unknown[];
|
|
35
|
+
needs_review: unknown[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Collect meeting extraction results using the portable result shape.
|
|
39
|
+
* Extraction errors intentionally propagate to the consumer: a host decides
|
|
40
|
+
* whether a failed meeting should be retried, surfaced for review, or stop a
|
|
41
|
+
* run, and the common package must not silently convert it to success.
|
|
42
|
+
*/
|
|
43
|
+
export declare function extractRoutinePersonalKgCandidates<TMeeting, TCandidate = unknown>(options: RoutinePersonalKgExtractionOptions<TMeeting, TCandidate>): RoutinePersonalKgExtractionResult<TCandidate>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable batch boundary for meeting-derived Personal KG extraction.
|
|
3
|
+
*
|
|
4
|
+
* A consumer supplies the meeting rule/extraction callback because source
|
|
5
|
+
* schemas, identity, redaction policy, and candidate shape are tenant-owned.
|
|
6
|
+
* This module owns the common collection/count contract and keeps the batch
|
|
7
|
+
* boundary independent from any database or model provider.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Collect meeting extraction results using the portable result shape.
|
|
11
|
+
* Extraction errors intentionally propagate to the consumer: a host decides
|
|
12
|
+
* whether a failed meeting should be retried, surfaced for review, or stop a
|
|
13
|
+
* run, and the common package must not silently convert it to success.
|
|
14
|
+
*/
|
|
15
|
+
export function extractRoutinePersonalKgCandidates(options) {
|
|
16
|
+
if (!options || typeof options !== 'object') {
|
|
17
|
+
throw new Error('routine Personal KG extraction options are required');
|
|
18
|
+
}
|
|
19
|
+
if (typeof options.date !== 'string' || options.date.trim() === '') {
|
|
20
|
+
throw new Error('routine Personal KG extraction date is required');
|
|
21
|
+
}
|
|
22
|
+
if (typeof options.sourceSystem !== 'string' || options.sourceSystem.trim() === '') {
|
|
23
|
+
throw new Error('routine Personal KG extraction sourceSystem is required');
|
|
24
|
+
}
|
|
25
|
+
if (typeof options.extract !== 'function') {
|
|
26
|
+
throw new Error('routine Personal KG extraction callback is required');
|
|
27
|
+
}
|
|
28
|
+
const adopted = [];
|
|
29
|
+
const rejected = [];
|
|
30
|
+
const needsReview = [];
|
|
31
|
+
const meetings = Array.isArray(options.meetings) ? options.meetings : [];
|
|
32
|
+
meetings.forEach((meeting, index) => {
|
|
33
|
+
const result = options.extract({ meeting, date: options.date, index });
|
|
34
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) {
|
|
35
|
+
throw new Error('routine Personal KG extraction result must be a non-null object');
|
|
36
|
+
}
|
|
37
|
+
const buckets = [
|
|
38
|
+
['adopted', result.adopted],
|
|
39
|
+
['rejected', result.rejected],
|
|
40
|
+
['needs_review', result.needs_review]
|
|
41
|
+
];
|
|
42
|
+
for (const [name, bucket] of buckets) {
|
|
43
|
+
if (bucket !== undefined && !Array.isArray(bucket)) {
|
|
44
|
+
throw new Error(`routine Personal KG extraction result ${name} must be an array`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
adopted.push(...(result.adopted ?? []));
|
|
48
|
+
rejected.push(...(result.rejected ?? []));
|
|
49
|
+
needsReview.push(...(result.needs_review ?? []));
|
|
50
|
+
});
|
|
51
|
+
const deduped = options.dedupe ? options.dedupe(adopted) : adopted;
|
|
52
|
+
const finalAdopted = Array.isArray(deduped) ? [...deduped] : adopted;
|
|
53
|
+
return {
|
|
54
|
+
date: options.date,
|
|
55
|
+
source_system: options.sourceSystem,
|
|
56
|
+
counts: {
|
|
57
|
+
adopted: finalAdopted.length,
|
|
58
|
+
rejected: rejected.length,
|
|
59
|
+
needs_review: needsReview.length
|
|
60
|
+
},
|
|
61
|
+
adopted: finalAdopted,
|
|
62
|
+
rejected,
|
|
63
|
+
needs_review: needsReview
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Awaitable } from './runtime-host.js';
|
|
2
|
+
/**
|
|
3
|
+
* Consumer-owned operations used by the routine endpoints.
|
|
4
|
+
*
|
|
5
|
+
* The implementation belongs to the application that owns Graph, tenant,
|
|
6
|
+
* credentials, and persistence. This public contract keeps the routine
|
|
7
|
+
* cycle independent from those details while giving each consumer one
|
|
8
|
+
* explicit seam to validate at composition time.
|
|
9
|
+
*/
|
|
10
|
+
export interface RoutineProductionPorts {
|
|
11
|
+
reconcile(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
12
|
+
compress(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
13
|
+
buildNightOutput(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
14
|
+
verify(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
15
|
+
recallGraph(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
16
|
+
recallPersonalKg(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
17
|
+
generate(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
18
|
+
recordUsage(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
19
|
+
evaluateMetrics(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
20
|
+
createImprovementCandidates(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
21
|
+
listKnowledgeReviews(input?: unknown, context?: unknown): Awaitable<unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validate a consumer's routine endpoint port set without resolving any
|
|
25
|
+
* consumer-specific dependency. The same instance is returned so class
|
|
26
|
+
* methods retain their receiver and injected state.
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertRoutineProductionPorts(value: unknown): RoutineProductionPorts;
|
|
29
|
+
/** Alias used by composition roots that prefer a constructor-shaped API. */
|
|
30
|
+
export declare function createRoutineProductionPorts(value: unknown): RoutineProductionPorts;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const REQUIRED_ROUTINE_PRODUCTION_PORTS = [
|
|
2
|
+
'reconcile',
|
|
3
|
+
'compress',
|
|
4
|
+
'buildNightOutput',
|
|
5
|
+
'verify',
|
|
6
|
+
'recallGraph',
|
|
7
|
+
'recallPersonalKg',
|
|
8
|
+
'generate',
|
|
9
|
+
'recordUsage',
|
|
10
|
+
'evaluateMetrics',
|
|
11
|
+
'createImprovementCandidates',
|
|
12
|
+
'listKnowledgeReviews'
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Validate a consumer's routine endpoint port set without resolving any
|
|
16
|
+
* consumer-specific dependency. The same instance is returned so class
|
|
17
|
+
* methods retain their receiver and injected state.
|
|
18
|
+
*/
|
|
19
|
+
export function assertRoutineProductionPorts(value) {
|
|
20
|
+
if (!value || typeof value !== 'object') {
|
|
21
|
+
throw new Error('routine production ports are required');
|
|
22
|
+
}
|
|
23
|
+
const ports = value;
|
|
24
|
+
for (const name of REQUIRED_ROUTINE_PRODUCTION_PORTS) {
|
|
25
|
+
if (typeof ports[name] !== 'function') {
|
|
26
|
+
throw new Error(`routine production port ${name} must be a function`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
/** Alias used by composition roots that prefer a constructor-shaped API. */
|
|
32
|
+
export function createRoutineProductionPorts(value) {
|
|
33
|
+
return assertRoutineProductionPorts(value);
|
|
34
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type NormalizedRunReceipt } from './run-receipt.js';
|
|
2
|
+
import { type RunReceiptPersistenceInput, type RunReceiptPort } from './run-receipt-port.js';
|
|
2
3
|
import { type Awaitable, type RuntimeHostContext, type RuntimeHostPorts } from './runtime-host.js';
|
|
3
4
|
export type RoutineScheduleKind = 'daily' | 'weekly';
|
|
4
5
|
export interface RoutineExpectationSchedule {
|
|
@@ -66,18 +67,8 @@ export interface RoutineRunnerPort {
|
|
|
66
67
|
start(input: RoutineRunnerStartInput): Awaitable<RoutineRunnerResult>;
|
|
67
68
|
retry?(input: RoutineRunnerRetryInput): Awaitable<RoutineRunnerResult>;
|
|
68
69
|
}
|
|
69
|
-
export
|
|
70
|
-
|
|
71
|
-
normalized: NormalizedRunReceipt;
|
|
72
|
-
context: RuntimeHostContext;
|
|
73
|
-
}
|
|
74
|
-
export interface RoutineReceiptPort {
|
|
75
|
-
persist(input: RoutineReceiptPersistenceInput): Awaitable<unknown>;
|
|
76
|
-
query(input: {
|
|
77
|
-
request: RoutineRunRequest;
|
|
78
|
-
context: RuntimeHostContext;
|
|
79
|
-
}): Awaitable<unknown>;
|
|
80
|
-
}
|
|
70
|
+
export type RoutineReceiptPersistenceInput = RunReceiptPersistenceInput<RuntimeHostContext>;
|
|
71
|
+
export type RoutineReceiptPort = RunReceiptPort<RoutineRunRequest, RuntimeHostContext>;
|
|
81
72
|
export interface RoutineRuntimePorts extends RuntimeHostPorts {
|
|
82
73
|
runner: RoutineRunnerPort;
|
|
83
74
|
receipt: RoutineReceiptPort;
|
package/dist/routine-runtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { normalizeRunReceipt } from './run-receipt.js';
|
|
4
|
+
import { assertRunReceiptPort } from './run-receipt-port.js';
|
|
4
5
|
import { resolveRuntimeHostContext } from './runtime-host.js';
|
|
5
6
|
function fail(index, field, message) {
|
|
6
7
|
throw new Error(`expectations[${index}].${field} ${message}`);
|
|
@@ -138,11 +139,7 @@ function assertRoutinePorts(ports) {
|
|
|
138
139
|
if (!ports.runner || typeof ports.runner !== 'object' || typeof ports.runner.start !== 'function') {
|
|
139
140
|
throw new Error('routine runner port with start is required');
|
|
140
141
|
}
|
|
141
|
-
|
|
142
|
-
|| typeof ports.receipt.persist !== 'function'
|
|
143
|
-
|| typeof ports.receipt.query !== 'function') {
|
|
144
|
-
throw new Error('routine receipt port with persist and query is required');
|
|
145
|
-
}
|
|
142
|
+
assertRunReceiptPort(ports.receipt, 'routine receipt');
|
|
146
143
|
}
|
|
147
144
|
export class RoutineRuntimeService {
|
|
148
145
|
ports;
|
package/dist/routines.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './routine-runtime.js';
|
|
2
2
|
export * from './routine-cycle.js';
|
|
3
3
|
export * from './routine-liveness.js';
|
|
4
|
+
export * from './routine-production-ports.js';
|
|
5
|
+
export * from './routine-personal-kg.js';
|
|
4
6
|
export type RoutineKind = 'ohayo' | 'oyasumi' | 'retro';
|
|
5
7
|
export type RoutineTarget = 'codex' | 'claude';
|
|
6
8
|
export interface ScheduleOptions {
|
package/dist/routines.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './routine-runtime.js';
|
|
2
2
|
export * from './routine-cycle.js';
|
|
3
3
|
export * from './routine-liveness.js';
|
|
4
|
+
export * from './routine-production-ports.js';
|
|
5
|
+
export * from './routine-personal-kg.js';
|
|
4
6
|
const ALL_KINDS = ['ohayo', 'oyasumi', 'retro'];
|
|
5
7
|
const DOW_TO_RRULE = {
|
|
6
8
|
MON: 'MO', TUE: 'TU', WED: 'WE', THU: 'TH', FRI: 'FR', SAT: 'SA', SUN: 'SU'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { NormalizedRunReceipt } from './run-receipt.js';
|
|
2
|
+
/**
|
|
3
|
+
* Shared host boundary for durable run-receipt ingest and query.
|
|
4
|
+
*
|
|
5
|
+
* The OSS package only defines the contract and validates the port shape.
|
|
6
|
+
* Storage, authentication, organization scoping, HTTP, and readback remain
|
|
7
|
+
* owned by the consumer that supplies this port.
|
|
8
|
+
*/
|
|
9
|
+
export interface RunReceiptPersistenceInput<Context = unknown> {
|
|
10
|
+
payload: unknown;
|
|
11
|
+
normalized: NormalizedRunReceipt;
|
|
12
|
+
context: Context;
|
|
13
|
+
}
|
|
14
|
+
export interface RunReceiptQueryInput<Request = unknown, Context = unknown> {
|
|
15
|
+
request: Request;
|
|
16
|
+
context: Context;
|
|
17
|
+
}
|
|
18
|
+
export interface RunReceiptPort<Request = unknown, Context = unknown, Persisted = unknown, Queried = unknown> {
|
|
19
|
+
persist(input: RunReceiptPersistenceInput<Context>): Persisted | PromiseLike<Persisted>;
|
|
20
|
+
query(input: RunReceiptQueryInput<Request, Context>): Queried | PromiseLike<Queried>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validate the common receipt boundary without assuming a storage provider.
|
|
24
|
+
* A label keeps the existing routine/automation diagnostics specific to their
|
|
25
|
+
* host-facing service while sharing the actual shape check.
|
|
26
|
+
*/
|
|
27
|
+
export declare function assertRunReceiptPort(port: unknown, label?: string): asserts port is RunReceiptPort;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate the common receipt boundary without assuming a storage provider.
|
|
3
|
+
* A label keeps the existing routine/automation diagnostics specific to their
|
|
4
|
+
* host-facing service while sharing the actual shape check.
|
|
5
|
+
*/
|
|
6
|
+
export function assertRunReceiptPort(port, label = 'run receipt') {
|
|
7
|
+
if (!port || typeof port !== 'object'
|
|
8
|
+
|| typeof port.persist !== 'function'
|
|
9
|
+
|| typeof port.query !== 'function') {
|
|
10
|
+
throw new Error(`${label} port with persist and query is required`);
|
|
11
|
+
}
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -163,6 +163,10 @@
|
|
|
163
163
|
"types": "./dist/run-receipt.d.ts",
|
|
164
164
|
"import": "./dist/run-receipt.js"
|
|
165
165
|
},
|
|
166
|
+
"./run-receipt-port": {
|
|
167
|
+
"types": "./dist/run-receipt-port.d.ts",
|
|
168
|
+
"import": "./dist/run-receipt-port.js"
|
|
169
|
+
},
|
|
166
170
|
"./routines": {
|
|
167
171
|
"types": "./dist/routines.d.ts",
|
|
168
172
|
"import": "./dist/routines.js"
|
|
@@ -191,6 +195,10 @@
|
|
|
191
195
|
"types": "./dist/meeting-source-connection.d.ts",
|
|
192
196
|
"import": "./dist/meeting-source-connection.js"
|
|
193
197
|
},
|
|
198
|
+
"./meeting-source-mcp-adapters": {
|
|
199
|
+
"types": "./dist/meeting-source-mcp-adapters.d.ts",
|
|
200
|
+
"import": "./dist/meeting-source-mcp-adapters.js"
|
|
201
|
+
},
|
|
194
202
|
"./meeting-source-reader": {
|
|
195
203
|
"types": "./dist/meeting-source-reader.d.ts",
|
|
196
204
|
"import": "./dist/meeting-source-reader.js"
|
|
@@ -399,6 +407,10 @@
|
|
|
399
407
|
"types": "./dist/meeting-source-sync.d.ts",
|
|
400
408
|
"import": "./dist/meeting-source-sync.js"
|
|
401
409
|
},
|
|
410
|
+
"./meeting-source-sync-common-adapter": {
|
|
411
|
+
"types": "./dist/meeting-source-sync-common-adapter.d.ts",
|
|
412
|
+
"import": "./dist/meeting-source-sync-common-adapter.js"
|
|
413
|
+
},
|
|
402
414
|
"./meeting-minutes-http": {
|
|
403
415
|
"types": "./dist/meeting-minutes-http.d.ts",
|
|
404
416
|
"import": "./dist/meeting-minutes-http.js"
|
|
@@ -608,5 +620,5 @@
|
|
|
608
620
|
"url": "https://github.com/Unson-LLC/brainbase/issues"
|
|
609
621
|
},
|
|
610
622
|
"homepage": "https://github.com/Unson-LLC/brainbase#readme",
|
|
611
|
-
"gitHead": "
|
|
623
|
+
"gitHead": "955bf4ef5249f607c4983f86c05ecae10ce7a67c"
|
|
612
624
|
}
|