@framers/agentos 0.13.63 → 0.13.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/types.d.ts +111 -81
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js.map +1 -1
- package/dist/cognition/memory/AgentMemory.d.ts +4 -2
- package/dist/cognition/memory/AgentMemory.d.ts.map +1 -1
- package/dist/cognition/memory/AgentMemory.js +3 -1
- package/dist/cognition/memory/AgentMemory.js.map +1 -1
- package/dist/cognition/memory/core/types.d.ts +1 -1
- package/dist/cognition/memory/core/types.d.ts.map +1 -1
- package/dist/cognition/memory/index.d.ts +1 -1
- package/dist/cognition/memory/index.d.ts.map +1 -1
- package/dist/cognition/memory/index.js.map +1 -1
- package/dist/cognition/memory/io/MarkdownExporter.d.ts +42 -8
- package/dist/cognition/memory/io/MarkdownExporter.d.ts.map +1 -1
- package/dist/cognition/memory/io/MarkdownExporter.js +196 -16
- package/dist/cognition/memory/io/MarkdownExporter.js.map +1 -1
- package/dist/cognition/memory/io/ObsidianExporter.d.ts +6 -4
- package/dist/cognition/memory/io/ObsidianExporter.d.ts.map +1 -1
- package/dist/cognition/memory/io/ObsidianExporter.js +5 -3
- package/dist/cognition/memory/io/ObsidianExporter.js.map +1 -1
- package/dist/cognition/memory/io/facade/Memory.d.ts +14 -3
- package/dist/cognition/memory/io/facade/Memory.d.ts.map +1 -1
- package/dist/cognition/memory/io/facade/Memory.js +19 -8
- package/dist/cognition/memory/io/facade/Memory.js.map +1 -1
- package/dist/cognition/memory/io/facade/index.d.ts +1 -1
- package/dist/cognition/memory/io/facade/index.d.ts.map +1 -1
- package/dist/cognition/memory/io/facade/types.d.ts +37 -0
- package/dist/cognition/memory/io/facade/types.d.ts.map +1 -1
- package/dist/cognition/memory/io/tools/MemoryAddTool.d.ts +4 -0
- package/dist/cognition/memory/io/tools/MemoryAddTool.d.ts.map +1 -1
- package/dist/cognition/memory/io/tools/MemoryAddTool.js +18 -4
- package/dist/cognition/memory/io/tools/MemoryAddTool.js.map +1 -1
- package/dist/cognition/substrate/memory/wiki/WikiCompiler.d.ts +5 -1
- package/dist/cognition/substrate/memory/wiki/WikiCompiler.d.ts.map +1 -1
- package/dist/cognition/substrate/memory/wiki/WikiCompiler.js +35 -10
- package/dist/cognition/substrate/memory/wiki/WikiCompiler.js.map +1 -1
- package/dist/cognition/substrate/memory/wiki/WikiMemoryStore.d.ts +23 -4
- package/dist/cognition/substrate/memory/wiki/WikiMemoryStore.d.ts.map +1 -1
- package/dist/cognition/substrate/memory/wiki/WikiMemoryStore.js +66 -35
- package/dist/cognition/substrate/memory/wiki/WikiMemoryStore.js.map +1 -1
- package/dist/cognition/substrate/memory/wiki/WikiPageId.d.ts +9 -7
- package/dist/cognition/substrate/memory/wiki/WikiPageId.d.ts.map +1 -1
- package/dist/cognition/substrate/memory/wiki/WikiPageId.js +19 -23
- package/dist/cognition/substrate/memory/wiki/WikiPageId.js.map +1 -1
- package/dist/core/utils/pathName.d.ts +59 -0
- package/dist/core/utils/pathName.d.ts.map +1 -0
- package/dist/core/utils/pathName.js +78 -0
- package/dist/core/utils/pathName.js.map +1 -0
- package/dist/io/channels/meetings/IMeetingBotProvider.d.ts +128 -0
- package/dist/io/channels/meetings/IMeetingBotProvider.d.ts.map +1 -0
- package/dist/io/channels/meetings/IMeetingBotProvider.js +2 -0
- package/dist/io/channels/meetings/IMeetingBotProvider.js.map +1 -0
- package/dist/io/channels/meetings/providers/mock.d.ts +46 -0
- package/dist/io/channels/meetings/providers/mock.d.ts.map +1 -0
- package/dist/io/channels/meetings/providers/mock.js +101 -0
- package/dist/io/channels/meetings/providers/mock.js.map +1 -0
- package/dist/io/channels/meetings/signing.d.ts +102 -0
- package/dist/io/channels/meetings/signing.d.ts.map +1 -0
- package/dist/io/channels/meetings/signing.js +141 -0
- package/dist/io/channels/meetings/signing.js.map +1 -0
- package/dist/io/channels/meetings/stores/memory.d.ts +88 -0
- package/dist/io/channels/meetings/stores/memory.d.ts.map +1 -0
- package/dist/io/channels/meetings/stores/memory.js +273 -0
- package/dist/io/channels/meetings/stores/memory.js.map +1 -0
- package/dist/io/channels/meetings/stores/types.d.ts +224 -0
- package/dist/io/channels/meetings/stores/types.d.ts.map +1 -0
- package/dist/io/channels/meetings/stores/types.js +35 -0
- package/dist/io/channels/meetings/stores/types.js.map +1 -0
- package/dist/io/channels/meetings/types.d.ts +200 -0
- package/dist/io/channels/meetings/types.d.ts.map +1 -0
- package/dist/io/channels/meetings/types.js +89 -0
- package/dist/io/channels/meetings/types.js.map +1 -0
- package/dist/io/voice-pipeline/types.d.ts +30 -0
- package/dist/io/voice-pipeline/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { MeetingBotCapabilities, MeetingLeaveReason, MeetingPlatform, MeetingRunRecord, NormalizedMeetingEvent } from './types.js';
|
|
2
|
+
/** What reached the product's events route: the provider authenticates and parses it. */
|
|
3
|
+
export interface ProviderEventContext {
|
|
4
|
+
/** The provider the route names. */
|
|
5
|
+
providerName: string;
|
|
6
|
+
/** The request's HTTP method. */
|
|
7
|
+
method: string;
|
|
8
|
+
/** The request's URL. */
|
|
9
|
+
url: string;
|
|
10
|
+
/** The request's headers. */
|
|
11
|
+
headers: Record<string, string | string[] | undefined>;
|
|
12
|
+
/** The request's body as it arrived, before any parse. */
|
|
13
|
+
body: string | Buffer;
|
|
14
|
+
/** The run the report names, when the route can read it before parsing (a path parameter). */
|
|
15
|
+
runId?: string;
|
|
16
|
+
}
|
|
17
|
+
/** A provider's verdict on whether a report is authentic. */
|
|
18
|
+
export interface ProviderVerificationResult {
|
|
19
|
+
/** Whether the report is authentic. */
|
|
20
|
+
valid: boolean;
|
|
21
|
+
/** Why the report was refused. */
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
/** The channel's events that one report holds. */
|
|
25
|
+
export interface ProviderParseResult {
|
|
26
|
+
/** The report's events, each with a stable id. */
|
|
27
|
+
events: NormalizedMeetingEvent[];
|
|
28
|
+
/** The report as the provider decoded it. */
|
|
29
|
+
rawData?: unknown;
|
|
30
|
+
}
|
|
31
|
+
/** What dispatch hands a provider. The tokens are minted at dispatch and never stored in clear. */
|
|
32
|
+
export interface DispatchInput {
|
|
33
|
+
/** The run to start: its link, its bot's name, its notice, its stop phrase and its limits. */
|
|
34
|
+
run: MeetingRunRecord;
|
|
35
|
+
/** The run's room: the server's url, the room's name and the bot's publish-only token. */
|
|
36
|
+
room: {
|
|
37
|
+
url: string;
|
|
38
|
+
name: string;
|
|
39
|
+
publishToken: string;
|
|
40
|
+
};
|
|
41
|
+
/** The per-run bearer token the worker presents on every report. */
|
|
42
|
+
workerToken: string;
|
|
43
|
+
/** Where the worker sends its reports. */
|
|
44
|
+
reportUrl: string;
|
|
45
|
+
}
|
|
46
|
+
/** What a provider answers a dispatch with; the manager keeps both on the run's dispatch record. */
|
|
47
|
+
export interface DispatchResult {
|
|
48
|
+
/** The worker that took the run. */
|
|
49
|
+
workerId?: string;
|
|
50
|
+
/** The provider's own reference for the run. */
|
|
51
|
+
providerRunRef?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A command the manager sends to a run's bot: `startCapture` and
|
|
55
|
+
* `suspendCapture` start and stop the audio it passes on, `postChat` posts to
|
|
56
|
+
* the meeting's chat, `leave` leaves the meeting with a reason, and `kill`
|
|
57
|
+
* stops the bot at once.
|
|
58
|
+
*/
|
|
59
|
+
export type ProviderCommand = {
|
|
60
|
+
name: 'startCapture';
|
|
61
|
+
} | {
|
|
62
|
+
name: 'suspendCapture';
|
|
63
|
+
} | {
|
|
64
|
+
name: 'postChat';
|
|
65
|
+
text: string;
|
|
66
|
+
} | {
|
|
67
|
+
name: 'leave';
|
|
68
|
+
reason: MeetingLeaveReason;
|
|
69
|
+
} | {
|
|
70
|
+
name: 'kill';
|
|
71
|
+
};
|
|
72
|
+
/** One run as a provider's workers report it. */
|
|
73
|
+
export interface ProviderUsage {
|
|
74
|
+
/** The run. */
|
|
75
|
+
runId: string;
|
|
76
|
+
/** Whether a worker still holds the run. */
|
|
77
|
+
alive: boolean;
|
|
78
|
+
/** The seconds the worker has counted for the run. */
|
|
79
|
+
seconds: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* One way of joining meetings. A provider ships as an extension pack with the
|
|
83
|
+
* kind `meeting-provider`; the host registers it on the manager.
|
|
84
|
+
*/
|
|
85
|
+
export interface IMeetingBotProvider {
|
|
86
|
+
/** The name the manager registers the provider under; runs and reports carry it. */
|
|
87
|
+
readonly name: string;
|
|
88
|
+
/** The platforms the provider joins. */
|
|
89
|
+
readonly platforms: readonly MeetingPlatform[];
|
|
90
|
+
/** What the provider declares about itself; the notice and the stop path follow from it. */
|
|
91
|
+
readonly capabilities: MeetingBotCapabilities;
|
|
92
|
+
/** The provider's own authentication of a report: a platform's signature scheme, or the worker's per-run token against the run's dispatch record. */
|
|
93
|
+
verifyEvent(ctx: ProviderEventContext, run: MeetingRunRecord | undefined): ProviderVerificationResult;
|
|
94
|
+
/**
|
|
95
|
+
* Turns a verified report into the channel's events, each with a stable id; throws on a report it cannot read. When the
|
|
96
|
+
* report was verified against a run, its caller applies only the events that name that run: a worker's per-run token
|
|
97
|
+
* speaks for its own run alone.
|
|
98
|
+
*/
|
|
99
|
+
parseEvent(ctx: ProviderEventContext): ProviderParseResult;
|
|
100
|
+
/** Tells a join url's platform, or why it is refused. */
|
|
101
|
+
checkJoinUrl(url: string): {
|
|
102
|
+
ok: true;
|
|
103
|
+
platform: MeetingPlatform;
|
|
104
|
+
} | {
|
|
105
|
+
ok: false;
|
|
106
|
+
message: string;
|
|
107
|
+
};
|
|
108
|
+
/** Hands the run to a worker with the room and the tokens minted for it; rejects when the run cannot start. */
|
|
109
|
+
dispatch(input: DispatchInput): Promise<DispatchResult>;
|
|
110
|
+
/**
|
|
111
|
+
* Sends a command to the run's bot. A provider passes on no audio of a run
|
|
112
|
+
* before the run's first `startCapture`, and none after a `suspendCapture`
|
|
113
|
+
* until the next `startCapture`: the manager sends `startCapture` only once
|
|
114
|
+
* the run's notice is confirmed posted and its stop monitor is healthy.
|
|
115
|
+
*/
|
|
116
|
+
command(runId: string, command: ProviderCommand): Promise<void>;
|
|
117
|
+
/** Deletes a provider's own copies of a run. Ours keep none; the method stands so a purge always has an answer. */
|
|
118
|
+
deleteRun(input: {
|
|
119
|
+
runId: string;
|
|
120
|
+
}): Promise<{
|
|
121
|
+
deleted: boolean;
|
|
122
|
+
}>;
|
|
123
|
+
/** Each run asked for (every run the provider knows when `runIds` is absent), alive or not, with the seconds it has counted. */
|
|
124
|
+
usage(input: {
|
|
125
|
+
runIds?: string[];
|
|
126
|
+
}): Promise<ProviderUsage[]>;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=IMeetingBotProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IMeetingBotProvider.d.ts","sourceRoot":"","sources":["../../../../src/io/channels/meetings/IMeetingBotProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IMeetingBotProvider.js","sourceRoot":"","sources":["../../../../src/io/channels/meetings/IMeetingBotProvider.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { DispatchInput, DispatchResult, IMeetingBotProvider, ProviderCommand, ProviderEventContext, ProviderParseResult, ProviderUsage, ProviderVerificationResult } from '../IMeetingBotProvider.js';
|
|
2
|
+
import type { MeetingBotCapabilities, MeetingPlatform, MeetingRunRecord, NormalizedMeetingEvent, ProviderEventType } from '../types.js';
|
|
3
|
+
/** Builds the context a worker's report would produce, for tests. */
|
|
4
|
+
export declare function mockEventContext(runId: string, type: ProviderEventType, data: NormalizedMeetingEvent['data'], at: number, workerToken: string): ProviderEventContext;
|
|
5
|
+
/** A provider for tests: it records dispatches and commands, and its reports are driven by the test through {@link mockEventContext}. */
|
|
6
|
+
export declare class MockMeetingBotProvider implements IMeetingBotProvider {
|
|
7
|
+
readonly name = "mock";
|
|
8
|
+
readonly platforms: readonly MeetingPlatform[];
|
|
9
|
+
readonly capabilities: MeetingBotCapabilities;
|
|
10
|
+
/** Each run's dispatch input, by run id. */
|
|
11
|
+
readonly dispatched: Map<string, DispatchInput>;
|
|
12
|
+
/** Each run's commands, in the order they were sent. */
|
|
13
|
+
readonly commands: Map<string, ProviderCommand[]>;
|
|
14
|
+
/** The run ids `deleteRun` was called with, in order. */
|
|
15
|
+
readonly deleted: string[];
|
|
16
|
+
/** What `usage()` answers; tests set it. */
|
|
17
|
+
alive: Map<string, number>;
|
|
18
|
+
/** When set, dispatch rejects with it (a worker with no slot). */
|
|
19
|
+
dispatchError: Error | undefined;
|
|
20
|
+
/** Runs inside `dispatch`, before it records anything: a test puts a race here (a cancel while the dispatch is in flight). */
|
|
21
|
+
beforeDispatch: ((input: DispatchInput) => Promise<void>) | undefined;
|
|
22
|
+
verifyEvent(ctx: ProviderEventContext, run: MeetingRunRecord | undefined): ProviderVerificationResult;
|
|
23
|
+
parseEvent(ctx: ProviderEventContext): ProviderParseResult;
|
|
24
|
+
checkJoinUrl(url: string): {
|
|
25
|
+
ok: true;
|
|
26
|
+
platform: MeetingPlatform;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
message: string;
|
|
30
|
+
};
|
|
31
|
+
dispatch(input: DispatchInput): Promise<DispatchResult>;
|
|
32
|
+
command(runId: string, command: ProviderCommand): Promise<void>;
|
|
33
|
+
deleteRun(input: {
|
|
34
|
+
runId: string;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
deleted: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
usage(input: {
|
|
39
|
+
runIds?: string[];
|
|
40
|
+
}): Promise<ProviderUsage[]>;
|
|
41
|
+
/** The token a run was dispatched with, for a test that drives its reports. */
|
|
42
|
+
tokenOf(runId: string): string;
|
|
43
|
+
/** For a test that wants the hash a run store would hold. */
|
|
44
|
+
static hash(token: string): string;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=mock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../../../../src/io/channels/meetings/providers/mock.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,0BAA0B,EAC3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAExI,qEAAqE;AACrE,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,iBAAiB,EACvB,IAAI,EAAE,sBAAsB,CAAC,MAAM,CAAC,EACpC,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,GAClB,oBAAoB,CAStB;AAMD,yIAAyI;AACzI,qBAAa,sBAAuB,YAAW,mBAAmB;IAChE,QAAQ,CAAC,IAAI,UAAU;IACvB,QAAQ,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAsC;IACpF,QAAQ,CAAC,YAAY,EAAE,sBAAsB,CAS3C;IACF,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,6BAAoC;IACvD,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,iCAAwC;IACzD,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAM;IAChC,4CAA4C;IAC5C,KAAK,sBAA6B;IAClC,kEAAkE;IAClE,aAAa,EAAE,KAAK,GAAG,SAAS,CAAC;IACjC,8HAA8H;IAC9H,cAAc,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IAEtE,WAAW,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,gBAAgB,GAAG,SAAS,GAAG,0BAA0B;IASrG,UAAU,CAAC,GAAG,EAAE,oBAAoB,GAAG,mBAAmB;IAgB1D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,eAAe,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAM7F,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAQvD,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/D,SAAS,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAKlE,KAAK,CAAC,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAKnE,+EAA+E;IAC/E,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAM9B,6DAA6D;IAC7D,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAGnC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { hashWorkerToken, workerTokenMatches } from '../signing.js';
|
|
3
|
+
/** Builds the context a worker's report would produce, for tests. */
|
|
4
|
+
export function mockEventContext(runId, type, data, at, workerToken) {
|
|
5
|
+
return {
|
|
6
|
+
providerName: 'mock',
|
|
7
|
+
method: 'POST',
|
|
8
|
+
url: `/events/mock/${runId}`,
|
|
9
|
+
headers: { authorization: `Bearer ${workerToken}` },
|
|
10
|
+
body: JSON.stringify({ runId, type, at, data }),
|
|
11
|
+
runId,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
// The join links the mock accepts: a Meet meeting code link, and a Teams meeting link on either of Microsoft's hosts.
|
|
15
|
+
const MEET = /^https:\/\/meet\.google\.com\/[a-z]{3}-[a-z]{4}-[a-z]{3}(\?.*)?$/;
|
|
16
|
+
const TEAMS = /^https:\/\/teams\.(microsoft\.com|live\.com)\/(l\/)?meet(up-join)?\//;
|
|
17
|
+
/** A provider for tests: it records dispatches and commands, and its reports are driven by the test through {@link mockEventContext}. */
|
|
18
|
+
export class MockMeetingBotProvider {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.name = 'mock';
|
|
21
|
+
this.platforms = ['google_meet', 'microsoft_teams'];
|
|
22
|
+
this.capabilities = {
|
|
23
|
+
joinsAsParticipant: true,
|
|
24
|
+
postsChat: true,
|
|
25
|
+
readsChat: true,
|
|
26
|
+
audioPath: 'livekit_room',
|
|
27
|
+
recordsAudio: false,
|
|
28
|
+
transcribes: false,
|
|
29
|
+
keeps: 'nothing',
|
|
30
|
+
stopPath: 'chat',
|
|
31
|
+
};
|
|
32
|
+
/** Each run's dispatch input, by run id. */
|
|
33
|
+
this.dispatched = new Map();
|
|
34
|
+
/** Each run's commands, in the order they were sent. */
|
|
35
|
+
this.commands = new Map();
|
|
36
|
+
/** The run ids `deleteRun` was called with, in order. */
|
|
37
|
+
this.deleted = [];
|
|
38
|
+
/** What `usage()` answers; tests set it. */
|
|
39
|
+
this.alive = new Map();
|
|
40
|
+
}
|
|
41
|
+
verifyEvent(ctx, run) {
|
|
42
|
+
const header = ctx.headers.authorization ?? ctx.headers.Authorization;
|
|
43
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
44
|
+
const token = value?.startsWith('Bearer ') ? value.slice(7) : undefined;
|
|
45
|
+
if (!token)
|
|
46
|
+
return { valid: false, error: 'no bearer token' };
|
|
47
|
+
if (!run?.dispatch)
|
|
48
|
+
return { valid: false, error: 'no dispatch record for the run' };
|
|
49
|
+
return workerTokenMatches(token, run.dispatch.workerTokenHash) ? { valid: true } : { valid: false, error: 'token mismatch' };
|
|
50
|
+
}
|
|
51
|
+
parseEvent(ctx) {
|
|
52
|
+
const body = typeof ctx.body === 'string' ? ctx.body : ctx.body.toString('utf8');
|
|
53
|
+
const raw = JSON.parse(body);
|
|
54
|
+
// the report's run, type and time, then a digest of the whole report: a repeated report keeps its id, and two reports of one type at one time do not share one
|
|
55
|
+
const digest = createHash('sha256').update(body).digest('hex').slice(0, 12);
|
|
56
|
+
return {
|
|
57
|
+
events: [{ eventId: `mock:${raw.runId}:${raw.type}:${raw.at}:${digest}`, runId: raw.runId, type: raw.type, at: raw.at, data: raw.data }],
|
|
58
|
+
rawData: raw,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
checkJoinUrl(url) {
|
|
62
|
+
if (MEET.test(url))
|
|
63
|
+
return { ok: true, platform: 'google_meet' };
|
|
64
|
+
if (TEAMS.test(url))
|
|
65
|
+
return { ok: true, platform: 'microsoft_teams' };
|
|
66
|
+
return { ok: false, message: 'not a Google Meet or Microsoft Teams join link' };
|
|
67
|
+
}
|
|
68
|
+
async dispatch(input) {
|
|
69
|
+
if (this.dispatchError)
|
|
70
|
+
throw this.dispatchError;
|
|
71
|
+
await this.beforeDispatch?.(input);
|
|
72
|
+
this.dispatched.set(input.run.runId, input);
|
|
73
|
+
this.alive.set(input.run.runId, 0);
|
|
74
|
+
return { workerId: 'mock-worker', providerRunRef: `mock-${input.run.runId}` };
|
|
75
|
+
}
|
|
76
|
+
async command(runId, command) {
|
|
77
|
+
const list = this.commands.get(runId) ?? [];
|
|
78
|
+
list.push(command);
|
|
79
|
+
this.commands.set(runId, list);
|
|
80
|
+
}
|
|
81
|
+
async deleteRun(input) {
|
|
82
|
+
this.deleted.push(input.runId);
|
|
83
|
+
return { deleted: false };
|
|
84
|
+
}
|
|
85
|
+
async usage(input) {
|
|
86
|
+
const ids = input.runIds ?? [...this.alive.keys()];
|
|
87
|
+
return ids.map((runId) => ({ runId, alive: this.alive.has(runId), seconds: this.alive.get(runId) ?? 0 }));
|
|
88
|
+
}
|
|
89
|
+
/** The token a run was dispatched with, for a test that drives its reports. */
|
|
90
|
+
tokenOf(runId) {
|
|
91
|
+
const d = this.dispatched.get(runId);
|
|
92
|
+
if (!d)
|
|
93
|
+
throw new Error(`no dispatch for ${runId}`);
|
|
94
|
+
return d.workerToken;
|
|
95
|
+
}
|
|
96
|
+
/** For a test that wants the hash a run store would hold. */
|
|
97
|
+
static hash(token) {
|
|
98
|
+
return hashWorkerToken(token);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=mock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.js","sourceRoot":"","sources":["../../../../../src/io/channels/meetings/providers/mock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module channels/meetings/signing
|
|
3
|
+
*
|
|
4
|
+
* Three ways the channel authenticates, kept apart: the envelope a host signs
|
|
5
|
+
* its outbound deliveries with, the per-run bearer token a worker presents on
|
|
6
|
+
* its reports, and the box that seals a run's meeting url at rest. A
|
|
7
|
+
* provider's own scheme (a platform's webhook signature) lives in that
|
|
8
|
+
* provider.
|
|
9
|
+
*/
|
|
10
|
+
/** The three header names of the envelope. A host passes its own (a product's branded names are the product's policy, never the library's). */
|
|
11
|
+
export interface EnvelopeHeaderNames {
|
|
12
|
+
/** The header that carries the delivery's id. */
|
|
13
|
+
id: string;
|
|
14
|
+
/** The header that carries the time the delivery was signed, in milliseconds since the epoch. */
|
|
15
|
+
timestamp: string;
|
|
16
|
+
/** The header that carries the signature. */
|
|
17
|
+
signature: string;
|
|
18
|
+
}
|
|
19
|
+
/** The header names the envelope uses when a host passes none. */
|
|
20
|
+
export declare const DEFAULT_ENVELOPE_HEADERS: Readonly<EnvelopeHeaderNames>;
|
|
21
|
+
/** How far a delivery's timestamp may be from the receiver's clock. */
|
|
22
|
+
export declare const ENVELOPE_WINDOW_MS = 300000;
|
|
23
|
+
/**
|
|
24
|
+
* `v1,` then the base64 HMAC-SHA256 of `<id>.<timestamp>.<payload>` under the destination's secret.
|
|
25
|
+
*
|
|
26
|
+
* @param secret - The destination's secret.
|
|
27
|
+
* @param id - The delivery's id.
|
|
28
|
+
* @param timestampMs - The time the delivery is signed, in milliseconds since the epoch.
|
|
29
|
+
* @param payload - The body exactly as it is sent.
|
|
30
|
+
*/
|
|
31
|
+
export declare function signEnvelope(secret: string, id: string, timestampMs: number, payload: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* The three headers of one delivery: its id, its time and its {@link signEnvelope} signature.
|
|
34
|
+
*
|
|
35
|
+
* @param secret - The destination's secret.
|
|
36
|
+
* @param id - The delivery's id.
|
|
37
|
+
* @param timestampMs - The time the delivery is signed, in milliseconds since the epoch.
|
|
38
|
+
* @param payload - The body exactly as it is sent.
|
|
39
|
+
* @param names - The header names; {@link DEFAULT_ENVELOPE_HEADERS} when absent.
|
|
40
|
+
*/
|
|
41
|
+
export declare function envelopeHeaders(secret: string, id: string, timestampMs: number, payload: string, names?: EnvelopeHeaderNames): Record<string, string>;
|
|
42
|
+
/**
|
|
43
|
+
* The receiving side: the three headers, the raw payload and the receiver's clock.
|
|
44
|
+
*
|
|
45
|
+
* @param secret - The destination's secret.
|
|
46
|
+
* @param headers - The delivery's headers.
|
|
47
|
+
* @param payload - The body exactly as it arrived, before any parse.
|
|
48
|
+
* @param nowMs - The receiver's clock, in milliseconds since the epoch.
|
|
49
|
+
* @param options - `windowMs`, how far the delivery's time may be from `nowMs` ({@link ENVELOPE_WINDOW_MS} when absent); `names`, the header names ({@link DEFAULT_ENVELOPE_HEADERS} when absent).
|
|
50
|
+
* @returns `{ valid: true }`, or `valid: false` with why: a header missing, a timestamp that is not a number, a timestamp outside the window, or a signature that does not match.
|
|
51
|
+
*/
|
|
52
|
+
export declare function verifyEnvelope(secret: string, headers: Record<string, string | string[] | undefined>, payload: string, nowMs: number, options?: {
|
|
53
|
+
windowMs?: number;
|
|
54
|
+
names?: EnvelopeHeaderNames;
|
|
55
|
+
}): {
|
|
56
|
+
valid: boolean;
|
|
57
|
+
error?: string;
|
|
58
|
+
};
|
|
59
|
+
/** 32 random bytes, base64url: the bearer a worker presents on every report of one run. */
|
|
60
|
+
export declare function mintWorkerToken(): string;
|
|
61
|
+
/** The SHA-256 of a worker token, in hex: what the run's dispatch record keeps, never the token itself. */
|
|
62
|
+
export declare function hashWorkerToken(token: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Whether `token` is the one whose hash is `hash`, compared in constant time.
|
|
65
|
+
*
|
|
66
|
+
* @param token - The bearer a report presented.
|
|
67
|
+
* @param hash - The {@link hashWorkerToken} hash the run's dispatch record keeps.
|
|
68
|
+
* @returns false as well when `hash` is not 64 hex digits.
|
|
69
|
+
*/
|
|
70
|
+
export declare function workerTokenMatches(token: string, hash: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Seals a run's meeting url at rest under one 32-byte key the host holds, with
|
|
73
|
+
* the library's AES-256-GCM sealing ({@link sealSecret}, {@link openSecret}): a
|
|
74
|
+
* fresh nonce per seal, so one value seals to a different text each time, and
|
|
75
|
+
* the text `v1.box.<base64url(nonce | ciphertext | tag)>`. A context given to
|
|
76
|
+
* `seal` (the run's id) is bound into the text, so a text moved to another
|
|
77
|
+
* run's row does not open there.
|
|
78
|
+
*/
|
|
79
|
+
export declare class SecretBox {
|
|
80
|
+
private readonly key;
|
|
81
|
+
/**
|
|
82
|
+
* @param key - The host's key: 32 bytes. The box keeps a copy, so a host that wipes or reuses its buffer afterwards does not change the box's key.
|
|
83
|
+
* @throws RangeError when `key` is not 32 bytes.
|
|
84
|
+
*/
|
|
85
|
+
constructor(key: Buffer);
|
|
86
|
+
/**
|
|
87
|
+
* Seals `plain`; the sealed text differs on every call.
|
|
88
|
+
*
|
|
89
|
+
* @param plain - The text to seal.
|
|
90
|
+
* @param context - What the text belongs to (the run's id), bound into the text as additional authenticated data; `open` must be given the same. Empty when absent.
|
|
91
|
+
*/
|
|
92
|
+
seal(plain: string, context?: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* Opens a text this box's key sealed.
|
|
95
|
+
*
|
|
96
|
+
* @param sealed - The sealed text.
|
|
97
|
+
* @param context - The context the text was sealed with; empty when absent.
|
|
98
|
+
* @throws SealedSecretError when the text is not a sealed text, was sealed under another key or with another context, or was changed.
|
|
99
|
+
*/
|
|
100
|
+
open(sealed: string, context?: string): string;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=signing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../../../src/io/channels/meetings/signing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,+IAA+I;AAC/I,MAAM,WAAW,mBAAmB;IAClC,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX,iGAAiG;IACjG,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,kEAAkE;AAClE,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,mBAAmB,CAA8F,CAAC;AAClK,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,SAAU,CAAC;AAE1C;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAGrG;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,mBAA8C,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAM/K;AAQD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EACtD,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,mBAAmB,CAAA;CAAO,GAC/D;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAcpC;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,2GAA2G;AAC3G,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMvE;AAKD;;;;;;;GAOG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IAEjC;;;OAGG;gBACS,GAAG,EAAE,MAAM;IAKvB;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM;IAIzC;;;;;;OAMG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM;CAG3C"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module channels/meetings/signing
|
|
3
|
+
*
|
|
4
|
+
* Three ways the channel authenticates, kept apart: the envelope a host signs
|
|
5
|
+
* its outbound deliveries with, the per-run bearer token a worker presents on
|
|
6
|
+
* its reports, and the box that seals a run's meeting url at rest. A
|
|
7
|
+
* provider's own scheme (a platform's webhook signature) lives in that
|
|
8
|
+
* provider.
|
|
9
|
+
*/
|
|
10
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
11
|
+
import { openSecret, sealSecret } from '../../../core/llm/auth/sealing.js';
|
|
12
|
+
/** The header names the envelope uses when a host passes none. */
|
|
13
|
+
export const DEFAULT_ENVELOPE_HEADERS = { id: 'x-webhook-id', timestamp: 'x-webhook-timestamp', signature: 'x-webhook-signature' };
|
|
14
|
+
/** How far a delivery's timestamp may be from the receiver's clock. */
|
|
15
|
+
export const ENVELOPE_WINDOW_MS = 300000;
|
|
16
|
+
/**
|
|
17
|
+
* `v1,` then the base64 HMAC-SHA256 of `<id>.<timestamp>.<payload>` under the destination's secret.
|
|
18
|
+
*
|
|
19
|
+
* @param secret - The destination's secret.
|
|
20
|
+
* @param id - The delivery's id.
|
|
21
|
+
* @param timestampMs - The time the delivery is signed, in milliseconds since the epoch.
|
|
22
|
+
* @param payload - The body exactly as it is sent.
|
|
23
|
+
*/
|
|
24
|
+
export function signEnvelope(secret, id, timestampMs, payload) {
|
|
25
|
+
const mac = createHmac('sha256', secret).update(`${id}.${timestampMs}.${payload}`).digest('base64');
|
|
26
|
+
return `v1,${mac}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The three headers of one delivery: its id, its time and its {@link signEnvelope} signature.
|
|
30
|
+
*
|
|
31
|
+
* @param secret - The destination's secret.
|
|
32
|
+
* @param id - The delivery's id.
|
|
33
|
+
* @param timestampMs - The time the delivery is signed, in milliseconds since the epoch.
|
|
34
|
+
* @param payload - The body exactly as it is sent.
|
|
35
|
+
* @param names - The header names; {@link DEFAULT_ENVELOPE_HEADERS} when absent.
|
|
36
|
+
*/
|
|
37
|
+
export function envelopeHeaders(secret, id, timestampMs, payload, names = DEFAULT_ENVELOPE_HEADERS) {
|
|
38
|
+
return {
|
|
39
|
+
[names.id]: id,
|
|
40
|
+
[names.timestamp]: String(timestampMs),
|
|
41
|
+
[names.signature]: signEnvelope(secret, id, timestampMs, payload),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// A header by its name, or by its name in lower case (Node's form for incoming headers); the first of a repeated header.
|
|
45
|
+
function header(headers, name) {
|
|
46
|
+
const v = headers[name] ?? headers[name.toLowerCase()];
|
|
47
|
+
return Array.isArray(v) ? v[0] : v;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The receiving side: the three headers, the raw payload and the receiver's clock.
|
|
51
|
+
*
|
|
52
|
+
* @param secret - The destination's secret.
|
|
53
|
+
* @param headers - The delivery's headers.
|
|
54
|
+
* @param payload - The body exactly as it arrived, before any parse.
|
|
55
|
+
* @param nowMs - The receiver's clock, in milliseconds since the epoch.
|
|
56
|
+
* @param options - `windowMs`, how far the delivery's time may be from `nowMs` ({@link ENVELOPE_WINDOW_MS} when absent); `names`, the header names ({@link DEFAULT_ENVELOPE_HEADERS} when absent).
|
|
57
|
+
* @returns `{ valid: true }`, or `valid: false` with why: a header missing, a timestamp that is not a number, a timestamp outside the window, or a signature that does not match.
|
|
58
|
+
*/
|
|
59
|
+
export function verifyEnvelope(secret, headers, payload, nowMs, options = {}) {
|
|
60
|
+
const names = options.names ?? DEFAULT_ENVELOPE_HEADERS;
|
|
61
|
+
const windowMs = options.windowMs ?? ENVELOPE_WINDOW_MS;
|
|
62
|
+
const id = header(headers, names.id);
|
|
63
|
+
const ts = header(headers, names.timestamp);
|
|
64
|
+
const sig = header(headers, names.signature);
|
|
65
|
+
if (!id || !ts || !sig)
|
|
66
|
+
return { valid: false, error: 'missing envelope header' };
|
|
67
|
+
const timestamp = Number(ts);
|
|
68
|
+
if (!Number.isFinite(timestamp))
|
|
69
|
+
return { valid: false, error: 'bad timestamp' };
|
|
70
|
+
if (Math.abs(nowMs - timestamp) > windowMs)
|
|
71
|
+
return { valid: false, error: 'outside the window' };
|
|
72
|
+
const expected = Buffer.from(signEnvelope(secret, id, timestamp, payload));
|
|
73
|
+
const given = Buffer.from(sig);
|
|
74
|
+
if (expected.length !== given.length || !timingSafeEqual(expected, given))
|
|
75
|
+
return { valid: false, error: 'signature mismatch' };
|
|
76
|
+
return { valid: true };
|
|
77
|
+
}
|
|
78
|
+
/** 32 random bytes, base64url: the bearer a worker presents on every report of one run. */
|
|
79
|
+
export function mintWorkerToken() {
|
|
80
|
+
return randomBytes(32).toString('base64url');
|
|
81
|
+
}
|
|
82
|
+
/** The SHA-256 of a worker token, in hex: what the run's dispatch record keeps, never the token itself. */
|
|
83
|
+
export function hashWorkerToken(token) {
|
|
84
|
+
return createHash('sha256').update(token).digest('hex');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Whether `token` is the one whose hash is `hash`, compared in constant time.
|
|
88
|
+
*
|
|
89
|
+
* @param token - The bearer a report presented.
|
|
90
|
+
* @param hash - The {@link hashWorkerToken} hash the run's dispatch record keeps.
|
|
91
|
+
* @returns false as well when `hash` is not 64 hex digits.
|
|
92
|
+
*/
|
|
93
|
+
export function workerTokenMatches(token, hash) {
|
|
94
|
+
// hex decoding stops at the first character that is not a hex digit, so a longer value that starts with the hash would decode to it
|
|
95
|
+
if (!/^[0-9a-f]{64}$/i.test(hash))
|
|
96
|
+
return false;
|
|
97
|
+
const a = Buffer.from(hashWorkerToken(token), 'hex');
|
|
98
|
+
const b = Buffer.from(hash, 'hex');
|
|
99
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
100
|
+
}
|
|
101
|
+
// The key id the box writes into each sealed text. It is part of the stored form: a text sealed under one id opens only under that id.
|
|
102
|
+
const SECRET_BOX_KEY_ID = 'box';
|
|
103
|
+
/**
|
|
104
|
+
* Seals a run's meeting url at rest under one 32-byte key the host holds, with
|
|
105
|
+
* the library's AES-256-GCM sealing ({@link sealSecret}, {@link openSecret}): a
|
|
106
|
+
* fresh nonce per seal, so one value seals to a different text each time, and
|
|
107
|
+
* the text `v1.box.<base64url(nonce | ciphertext | tag)>`. A context given to
|
|
108
|
+
* `seal` (the run's id) is bound into the text, so a text moved to another
|
|
109
|
+
* run's row does not open there.
|
|
110
|
+
*/
|
|
111
|
+
export class SecretBox {
|
|
112
|
+
/**
|
|
113
|
+
* @param key - The host's key: 32 bytes. The box keeps a copy, so a host that wipes or reuses its buffer afterwards does not change the box's key.
|
|
114
|
+
* @throws RangeError when `key` is not 32 bytes.
|
|
115
|
+
*/
|
|
116
|
+
constructor(key) {
|
|
117
|
+
if (key.length !== 32)
|
|
118
|
+
throw new RangeError('SecretBox needs a 32-byte key');
|
|
119
|
+
this.key = { id: SECRET_BOX_KEY_ID, key: Buffer.from(key) };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Seals `plain`; the sealed text differs on every call.
|
|
123
|
+
*
|
|
124
|
+
* @param plain - The text to seal.
|
|
125
|
+
* @param context - What the text belongs to (the run's id), bound into the text as additional authenticated data; `open` must be given the same. Empty when absent.
|
|
126
|
+
*/
|
|
127
|
+
seal(plain, context = '') {
|
|
128
|
+
return sealSecret(plain, this.key.key, this.key.id, context);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Opens a text this box's key sealed.
|
|
132
|
+
*
|
|
133
|
+
* @param sealed - The sealed text.
|
|
134
|
+
* @param context - The context the text was sealed with; empty when absent.
|
|
135
|
+
* @throws SealedSecretError when the text is not a sealed text, was sealed under another key or with another context, or was changed.
|
|
136
|
+
*/
|
|
137
|
+
open(sealed, context = '') {
|
|
138
|
+
return Buffer.from(openSecret(sealed, [this.key], context)).toString('utf8');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.js","sourceRoot":"","sources":["../../../../src/io/channels/meetings/signing.ts"],"names":[],"mappings":"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"}
|