@gotcos/glasses-server 6.46.0 → 6.47.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/CHANGELOG.md +21 -0
- package/package.json +6 -2
- package/server/index.ts +76 -0
- package/server/lib/cos-operations-meetings.ts +99 -8
- package/server/lib/fireflies-client.ts +862 -0
- package/server/lib/fireflies-key.ts +182 -0
- package/server/lib/g2-ops-handoff.ts +15 -1
- package/server/lib/imported-library-rows.ts +616 -0
- package/server/lib/imported-meeting-library.ts +608 -0
- package/server/lib/maintenance-lifecycle.ts +14 -0
- package/server/lib/meeting-actions-store.ts +478 -0
- package/server/lib/meeting-actions.ts +2583 -0
- package/server/lib/meeting-corrections.ts +32 -1
- package/server/lib/meeting-decisions.ts +223 -0
- package/server/lib/meeting-engine/align.ts +167 -0
- package/server/lib/meeting-engine/attribute.ts +265 -0
- package/server/lib/meeting-engine/evidence.ts +428 -0
- package/server/lib/meeting-engine/pairing.ts +327 -0
- package/server/lib/meeting-engine/render.ts +694 -0
- package/server/lib/meeting-engine/split.ts +242 -0
- package/server/lib/meeting-engine/worker.ts +238 -0
- package/server/lib/meeting-engine-mode.ts +197 -0
- package/server/lib/meeting-file-guards.ts +141 -0
- package/server/lib/meeting-import.ts +763 -0
- package/server/lib/meeting-library-search.ts +146 -11
- package/server/lib/meeting-parse.ts +184 -0
- package/server/lib/meeting-store.ts +108 -275
- package/server/lib/meeting-suggestion-sides.ts +242 -0
- package/server/lib/morning-brief-runtime.ts +20 -8
- package/server/lib/pipeline-runner.ts +227 -0
- package/server/lib/voice-evidence-guard.ts +87 -0
- package/server/routes/fireflies-key.ts +102 -0
- package/server/routes/meeting-actions.ts +82 -0
- package/server/routes/meeting-engine.ts +52 -0
- package/server/routes/meeting-import.ts +67 -0
- package/server/routes/meeting-suggestions.ts +66 -0
- package/server/routes/meeting.ts +117 -10
- package/server/routes/meetings.ts +205 -37
- package/server/routes/voice.ts +18 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Filesystem guards shared by every meeting library on this box.
|
|
2
|
+
//
|
|
3
|
+
// These were private methods on MeetingStore. 6.47.0 gives the imported library
|
|
4
|
+
// its own root under the data home, and it needs the SAME guards rather than a
|
|
5
|
+
// second, subtly different copy of them: a symlinked month folder, a meeting
|
|
6
|
+
// file that is really a link to ~/.ssh/id_ed25519, or a "markdown" file of
|
|
7
|
+
// 400 MB are the three ways a directory of user-named files becomes a read
|
|
8
|
+
// primitive, and each is closed here once.
|
|
9
|
+
//
|
|
10
|
+
// Every rule is carried over unchanged:
|
|
11
|
+
// - lstat before open, so a symlink is refused rather than followed;
|
|
12
|
+
// - realpath containment, and the parent must be the directory we meant, so
|
|
13
|
+
// `../` in a name cannot climb out;
|
|
14
|
+
// - O_NOFOLLOW on the open itself, which closes the window between the lstat
|
|
15
|
+
// and the open;
|
|
16
|
+
// - a byte ceiling, because the caller decides what "too big to read" means
|
|
17
|
+
// and the answer differs for markdown and for a sidecar.
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
closeSync,
|
|
21
|
+
constants,
|
|
22
|
+
existsSync,
|
|
23
|
+
fstatSync,
|
|
24
|
+
lstatSync,
|
|
25
|
+
openSync,
|
|
26
|
+
readFileSync,
|
|
27
|
+
readSync,
|
|
28
|
+
realpathSync,
|
|
29
|
+
} from 'node:fs'
|
|
30
|
+
import { dirname, join, sep } from 'node:path'
|
|
31
|
+
|
|
32
|
+
/** Markdown ceiling. A meeting record past this is not a meeting record. */
|
|
33
|
+
export const MAX_MEETING_BYTES = 10 * 1024 * 1024
|
|
34
|
+
|
|
35
|
+
export class UnsafeMeetingDirectoryError extends Error {
|
|
36
|
+
constructor(message: string) {
|
|
37
|
+
super(message)
|
|
38
|
+
this.name = 'UnsafeMeetingDirectoryError'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isContained(parent: string, child: string): boolean {
|
|
43
|
+
return child === parent || child.startsWith(`${parent}${sep}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Realpath of an existing root, or null when it does not exist.
|
|
48
|
+
*
|
|
49
|
+
* `unsafe` builds the error thrown when the root is a symlink or not a
|
|
50
|
+
* directory, so each caller can keep its own error contract: MeetingStore
|
|
51
|
+
* throws a MeetingStoreError with a status and a code, and the imported library
|
|
52
|
+
* throws its own.
|
|
53
|
+
*/
|
|
54
|
+
export function existingRootRealpath(
|
|
55
|
+
root: string,
|
|
56
|
+
unsafe: () => Error = () => new UnsafeMeetingDirectoryError(`Unsafe meeting directory: ${root}`),
|
|
57
|
+
): string | null {
|
|
58
|
+
if (!existsSync(root)) return null
|
|
59
|
+
const stat = lstatSync(root)
|
|
60
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
61
|
+
throw unsafe()
|
|
62
|
+
}
|
|
63
|
+
return realpathSync(root)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function safeDirectoryRealpath(path: string, parentReal: string): string | null {
|
|
67
|
+
try {
|
|
68
|
+
const stat = lstatSync(path)
|
|
69
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) return null
|
|
70
|
+
const real = realpathSync(path)
|
|
71
|
+
return isContained(parentReal, real) && dirname(real) === parentReal ? real : null
|
|
72
|
+
} catch {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Read a whole file inside a verified directory, or null.
|
|
79
|
+
*
|
|
80
|
+
* `maxBytes` defaults to the markdown ceiling. The imported library passes a
|
|
81
|
+
* larger one for its JSON sidecars, which legitimately run to megabytes.
|
|
82
|
+
*/
|
|
83
|
+
export function safeReadFile(
|
|
84
|
+
directory: string,
|
|
85
|
+
directoryReal: string,
|
|
86
|
+
filename: string,
|
|
87
|
+
maxBytes: number = MAX_MEETING_BYTES,
|
|
88
|
+
): string | null {
|
|
89
|
+
const filepath = join(directory, filename)
|
|
90
|
+
let fd: number | null = null
|
|
91
|
+
try {
|
|
92
|
+
const linkStat = lstatSync(filepath)
|
|
93
|
+
if (linkStat.isSymbolicLink() || !linkStat.isFile()) return null
|
|
94
|
+
const real = realpathSync(filepath)
|
|
95
|
+
if (!isContained(directoryReal, real) || dirname(real) !== directoryReal) return null
|
|
96
|
+
fd = openSync(filepath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0))
|
|
97
|
+
const stat = fstatSync(fd)
|
|
98
|
+
if (!stat.isFile() || stat.size > maxBytes) return null
|
|
99
|
+
return readFileSync(fd, 'utf8')
|
|
100
|
+
} catch {
|
|
101
|
+
return null
|
|
102
|
+
} finally {
|
|
103
|
+
if (fd !== null) {
|
|
104
|
+
try { closeSync(fd) } catch { /* already closed */ }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Read only the first `bytes`, with the same guards as safeReadFile.
|
|
110
|
+
*
|
|
111
|
+
* Exists so a list can lift one field out of a sidecar without reading it
|
|
112
|
+
* whole: sidecars run to megabytes (1.3 MB for a 32-minute meeting) and would
|
|
113
|
+
* also trip the markdown ceiling. */
|
|
114
|
+
export function safeReadFileHead(
|
|
115
|
+
directory: string,
|
|
116
|
+
directoryReal: string,
|
|
117
|
+
filename: string,
|
|
118
|
+
bytes: number,
|
|
119
|
+
): string | null {
|
|
120
|
+
const filepath = join(directory, filename)
|
|
121
|
+
let fd: number | null = null
|
|
122
|
+
try {
|
|
123
|
+
const linkStat = lstatSync(filepath)
|
|
124
|
+
if (linkStat.isSymbolicLink() || !linkStat.isFile()) return null
|
|
125
|
+
const real = realpathSync(filepath)
|
|
126
|
+
if (!isContained(directoryReal, real) || dirname(real) !== directoryReal) return null
|
|
127
|
+
fd = openSync(filepath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0))
|
|
128
|
+
const stat = fstatSync(fd)
|
|
129
|
+
if (!stat.isFile()) return null
|
|
130
|
+
const buffer = Buffer.alloc(Math.min(bytes, stat.size))
|
|
131
|
+
if (buffer.length === 0) return ''
|
|
132
|
+
const read = readSync(fd, buffer, 0, buffer.length, 0)
|
|
133
|
+
return buffer.subarray(0, read).toString('utf8')
|
|
134
|
+
} catch {
|
|
135
|
+
return null
|
|
136
|
+
} finally {
|
|
137
|
+
if (fd !== null) {
|
|
138
|
+
try { closeSync(fd) } catch { /* already closed */ }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|