@gotcos/glasses-server 6.99.16 → 6.99.20
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 +46 -0
- package/package.json +1 -1
- package/server/index.ts +21 -0
- package/server/lib/ask-work-plan.ts +116 -0
- package/server/lib/batch-transcript-quality.ts +2 -0
- package/server/lib/drop-store.ts +16 -2
- package/server/lib/meeting-ask-runtime.ts +1 -1
- package/server/lib/meeting-batch-transcribe.ts +40 -1
- package/server/lib/ping-sources.ts +6 -2
- package/server/lib/prep-runtime.ts +915 -0
- package/server/lib/prep-store.ts +226 -0
- package/server/lib/stt-resolver.ts +26 -2
- package/server/lib/transcript-cleanup.ts +11 -2
- package/server/lib/work-orchestrator/runtime.ts +8 -2
- package/server/routes/ask-work-plan.ts +114 -0
- package/server/routes/health.ts +8 -0
- package/server/routes/prep.ts +81 -0
- package/server/routes/welcome-context.ts +29 -1
- package/server/routes/work-orchestrator.ts +57 -0
- package/server/web-control/panes/cos-chat.css +22 -0
- package/server/web-control/panes/cos-chat.js +615 -2
- package/server/web-control/panes/meetings.css +30 -0
- package/server/web-control/panes/meetings.js +230 -7
- package/shared/meeting-ask.ts +6 -2
- package/shared/prep.ts +317 -0
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* POST /api/work/orchestrator/input {workID, meetingWorkIDs?, snapshot?}
|
|
6
6
|
* Stores Control's snapshot when one is sent (Phase A: Control pushes, the server keeps the copy), then answers
|
|
7
7
|
* the card's planner rows, what was left out and why, and the usage snapshot.
|
|
8
|
+
* POST /api/work/orchestrator/snapshot {workID, revision, serverInstanceId, snapshot}
|
|
9
|
+
* Bound to this server and a current canonical card revision. Stores validated Control evidence only;
|
|
10
|
+
* no discovery, usage refresh, model, link/receipt write or dispatch.
|
|
8
11
|
* GET /api/work/orchestrator/usage[?refresh=1]
|
|
9
12
|
* The usage snapshot per provider, read on demand: Claude and Codex are re-read when their reading is older than
|
|
10
13
|
* 10 minutes (`refresh=1`: 2 minutes). No timer reads usage unless COS_WORK_USAGE_POLL=1.
|
|
@@ -14,6 +17,11 @@ import { parseSnapshot, type ControlSnapshot } from '../lib/work-orchestrator/sn
|
|
|
14
17
|
import type { OrchestratorInput } from '../lib/work-orchestrator/input.js'
|
|
15
18
|
import type { UsageMonitor, UsageView } from '../lib/work-orchestrator/usage.js'
|
|
16
19
|
import { orchestratorInputFor, orchestratorSnapshots, orchestratorUsage } from '../lib/work-orchestrator/runtime.js'
|
|
20
|
+
import { listBoard, type TaskBoardRow } from '../lib/task-store.js'
|
|
21
|
+
import { createWorkBoardReader } from '../lib/work-board-reader.js'
|
|
22
|
+
import { getServerInstanceId } from '../lib/server-instance-id.js'
|
|
23
|
+
import { maintenanceAdmissionsOpen, acquireMaintenanceWork } from '../lib/maintenance-lifecycle.js'
|
|
24
|
+
import { AskWorkPlanError, resolveAskWorkTask } from '../lib/ask-work-plan.js'
|
|
17
25
|
|
|
18
26
|
const WORK_ID = /^task:[a-z_]+:[a-f0-9]{12}$/
|
|
19
27
|
const MEETING_ID = /^meeting:[^\u0000-\u001f]{1,250}$/
|
|
@@ -24,6 +32,10 @@ export interface WorkOrchestratorDeps {
|
|
|
24
32
|
input: (workID: string, meetingWorkIDs: string[]) => Promise<OrchestratorInput>
|
|
25
33
|
usage: (refresh: boolean) => Promise<UsageView[]>
|
|
26
34
|
now: () => number
|
|
35
|
+
board: () => Promise<TaskBoardRow[]>
|
|
36
|
+
serverInstanceId: () => string | null
|
|
37
|
+
admissions: () => boolean
|
|
38
|
+
lease: () => { release(): void }
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
/** GET /usage reads on demand: a reading older than 10 minutes is re-read (`refresh=1`: 2 minutes). */
|
|
@@ -34,12 +46,57 @@ const DEFAULTS: WorkOrchestratorDeps = {
|
|
|
34
46
|
input: orchestratorInputFor,
|
|
35
47
|
usage: refresh => readUsage(refresh),
|
|
36
48
|
now: () => Date.now(),
|
|
49
|
+
board: () => createWorkBoardReader(() => listBoard()).read(0),
|
|
50
|
+
serverInstanceId: getServerInstanceId,
|
|
51
|
+
admissions: maintenanceAdmissionsOpen,
|
|
52
|
+
lease: () => acquireMaintenanceWork('api_mutation'),
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
export function createWorkOrchestratorRouter(given: Partial<WorkOrchestratorDeps> = {}) {
|
|
40
56
|
const deps = { ...DEFAULTS, ...given }
|
|
41
57
|
const router = Router()
|
|
42
58
|
router.use('/work/orchestrator', (_req, res, next) => { res.set('Cache-Control', 'private, no-store'); next() })
|
|
59
|
+
router.post('/work/orchestrator/snapshot', async (req, res) => {
|
|
60
|
+
const body = req.body
|
|
61
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)
|
|
62
|
+
|| Object.keys(body).some(k => !['workID', 'revision', 'serverInstanceId', 'snapshot'].includes(k))
|
|
63
|
+
|| typeof body.workID !== 'string' || body.workID.length > 256 || !WORK_ID.test(body.workID)
|
|
64
|
+
|| typeof body.revision !== 'string' || !/^[a-f0-9]{64}$/.test(body.revision)
|
|
65
|
+
|| typeof body.serverInstanceId !== 'string'
|
|
66
|
+
|| !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(body.serverInstanceId)) {
|
|
67
|
+
return res.status(400).json({ error: 'invalid_snapshot_binding' })
|
|
68
|
+
}
|
|
69
|
+
if (Buffer.byteLength(JSON.stringify(body)) > ORCHESTRATOR_BODY_BYTES) return res.status(413).json({ error: 'snapshot_too_large' })
|
|
70
|
+
let snapshot: ControlSnapshot
|
|
71
|
+
try { snapshot = parseSnapshot(body.snapshot, deps.now()) }
|
|
72
|
+
catch { return res.status(400).json({ error: 'invalid_snapshot' }) }
|
|
73
|
+
const binding = () => {
|
|
74
|
+
const id = deps.serverInstanceId()
|
|
75
|
+
if (!id) throw new AskWorkPlanError('server_instance_unavailable', 503)
|
|
76
|
+
if (id !== body.serverInstanceId) throw new AskWorkPlanError('server_instance_changed', 409)
|
|
77
|
+
if (!deps.admissions()) throw new AskWorkPlanError('maintenance', 503)
|
|
78
|
+
return id
|
|
79
|
+
}
|
|
80
|
+
let lease: ReturnType<WorkOrchestratorDeps['lease']> | undefined
|
|
81
|
+
try {
|
|
82
|
+
binding()
|
|
83
|
+
lease = deps.lease()
|
|
84
|
+
let rows: TaskBoardRow[]
|
|
85
|
+
try { rows = await deps.board() }
|
|
86
|
+
catch { throw new AskWorkPlanError('work_board_unavailable', 503) }
|
|
87
|
+
resolveAskWorkTask(body, rows)
|
|
88
|
+
const serverInstanceId = binding()
|
|
89
|
+
if (res.destroyed) return
|
|
90
|
+
// This is the same paired-client evidence cache as /input, not execution authority. parseSnapshot
|
|
91
|
+
// retains only its known fields; arbitrary automatic/forkable/capability fields never enter the cache.
|
|
92
|
+
deps.putSnapshot(snapshot)
|
|
93
|
+
return res.json({ protocolVersion: 1, accepted: true, workID: body.workID, revision: body.revision, serverInstanceId })
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (res.destroyed) return
|
|
96
|
+
const known = error instanceof AskWorkPlanError
|
|
97
|
+
return res.status(known ? error.status : 503).json({ error: known ? error.code : 'snapshot_unavailable' })
|
|
98
|
+
} finally { lease?.release() }
|
|
99
|
+
})
|
|
43
100
|
router.post('/work/orchestrator/input', async (req, res) => {
|
|
44
101
|
const body = req.body
|
|
45
102
|
const meetings: unknown = body?.meetingWorkIDs ?? []
|
|
@@ -172,3 +172,25 @@
|
|
|
172
172
|
/* 6.99.16: the COS thread's one quiet line, under the title row. */
|
|
173
173
|
.cc-thread { flex-basis: 100%; margin: 0; font-size: 12.5px; line-height: 1.35; color: var(--tertiary); }
|
|
174
174
|
.cc-thread:empty { display: none; }
|
|
175
|
+
|
|
176
|
+
/* 6.99.19: the Prep composer, above the chat's own. The live meeting view's panel, Files row and composer (meetings.css
|
|
177
|
+
.mt-panel, .mt-files, .mt-composer), so it reads as the same tool. */
|
|
178
|
+
.cc-prep { padding: 10px 0 2px; }
|
|
179
|
+
.cc-prep-panel { display: grid; gap: 6px; }
|
|
180
|
+
.cc-prep-panel .mt-ask-line { margin: 0 0 4px; }
|
|
181
|
+
.cc-prep-title { min-width: 0; overflow-wrap: anywhere; }
|
|
182
|
+
.cc-prep-head-acts { display: flex; flex: none; gap: 4px 14px; flex-wrap: wrap; justify-content: flex-end; }
|
|
183
|
+
.cc-prep-panel .mt-files { max-width: none; margin: 0; }
|
|
184
|
+
.cc-prep-panel .mt-files-list li.mt-up { min-height: 44px; align-content: center; }
|
|
185
|
+
.cc-prep-form { margin-top: 2px; }
|
|
186
|
+
.cc-prep-form .field { min-height: 76px; max-height: 220px; }
|
|
187
|
+
.cc-prep-acts { display: flex; align-items: center; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
|
|
188
|
+
.cc-prep-count { flex: 1; min-width: 0; font-size: 12px; }
|
|
189
|
+
.cc-prep-add { position: relative; cursor: pointer; }
|
|
190
|
+
.cc-prep-add:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
191
|
+
.cc-prep-acts .cx-btn { height: 40px; }
|
|
192
|
+
.cc-prep-note { margin: 0; font-size: 13px; }
|
|
193
|
+
.cc-prep-note[hidden] { display: none; }
|
|
194
|
+
.cc-prep.dragging .cc-prep-panel { outline: 2px dashed var(--accent); outline-offset: 4px; }
|
|
195
|
+
.cc-prep { scroll-margin-top: calc(64px + var(--safe-top, 0px)); }
|
|
196
|
+
@media (min-width: 860px) { .msg-right .cc-prep { padding: 10px 16px 0; max-height: 55%; overflow-y: auto; flex: none; } }
|