@qvac/core 0.1.0 → 0.1.1

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.
@@ -0,0 +1,28 @@
1
+ // Hand-written declarations for the generated schema/helpers.js. assistant's own
2
+ // tsconfig uses allowJs + checkJs:false, but strict consumers (app-runtime,
3
+ // mobile) without allowJs need these types to import the module without TS7016.
4
+ // The `*Map` helpers are hyperdb map-reduce emitters: they return an array of
5
+ // plain record rows (e.g. `[{ chatId, type, seq }]`), never a Map. Rows stay
6
+ // `Record<string, any>` to match the previously-inferred shape and keep existing
7
+ // .ts callers permissive.
8
+ export function chunkTypeOrdinal(type: string): number
9
+ export function chunksByTypeMap(record: any): ReadonlyArray<Record<string, any>>
10
+ export function activeAgentsMap(record: any): ReadonlyArray<Record<string, any>>
11
+ export function activeChatsMap(record: any): ReadonlyArray<Record<string, any>>
12
+ export function activeChatsByAgentMap(record: any): ReadonlyArray<Record<string, any>>
13
+ export function activeChatGroupsMap(record: any): ReadonlyArray<Record<string, any>>
14
+ export function activeKnowledgeByChatMap(record: any): ReadonlyArray<Record<string, any>>
15
+ export function activeToolConfigsByAgentMap(record: any): ReadonlyArray<Record<string, any>>
16
+ export function toolActivityByAgentMap(record: any): ReadonlyArray<Record<string, any>>
17
+ export function runRequestsMap(record: any): ReadonlyArray<Record<string, any>>
18
+ export const MEDIA_KINDS: readonly ['audio', 'image', 'video', 'other']
19
+ export const AUDIO_EXTENSIONS: readonly string[]
20
+ export function mediaKindOf(
21
+ mimeType?: string,
22
+ fileName?: string
23
+ ): 'audio' | 'image' | 'video' | 'other'
24
+ export function mediaKindOrdinal(kind: string): number
25
+ export function attachmentsByMediaKindMap(record: any): ReadonlyArray<Record<string, any>>
26
+ export function attachmentsByMediaKindOriginMap(record: any): ReadonlyArray<Record<string, any>>
27
+ export function historyChunksMap(record: any): ReadonlyArray<Record<string, any>>
28
+ export function liveWritersByMostRecentMap(record: any): ReadonlyArray<Record<string, any>>
@@ -0,0 +1,176 @@
1
+ // imported by the generated spec at runtime (dbns.require embeds a relative path) — stays plain .js
2
+ const historyChunkTypes = ['user-message', 'run-request', 'content', 'attachment', 'compaction']
3
+
4
+ // index keys hold the ordinal, kept in sync by hand with CHUNK_TYPES order in content.ts
5
+ export function chunkTypeOrdinal(type) {
6
+ switch (type) {
7
+ case 'user-message':
8
+ return 1
9
+ case 'content':
10
+ return 2
11
+ case 'thinking':
12
+ return 3
13
+ case 'tool-call':
14
+ return 4
15
+ case 'tool-result':
16
+ return 5
17
+ case 'attachment':
18
+ return 6
19
+ case 'approval-request':
20
+ return 7
21
+ case 'run-status':
22
+ return 8
23
+ case 'run-request':
24
+ return 9
25
+ case 'sources':
26
+ return 10
27
+ case 'compaction':
28
+ return 11
29
+ default:
30
+ throw new Error('Unknown chunk type: ' + type)
31
+ }
32
+ }
33
+
34
+ export function chunksByTypeMap(record) {
35
+ if (record.type === undefined) return []
36
+ return [{ chatId: record.chatId, type: chunkTypeOrdinal(record.type), seq: record.seq }]
37
+ }
38
+
39
+ export function activeAgentsMap(record) {
40
+ if (record.deletedAt) return []
41
+ return [{ createdAt: record.createdAt }]
42
+ }
43
+
44
+ export function activeChatsMap(record) {
45
+ if (record.deletedAt) return []
46
+ return [{ createdAt: record.createdAt }]
47
+ }
48
+
49
+ export function activeChatGroupsMap(record) {
50
+ if (record.deletedAt) return []
51
+ return [{ createdAt: record.createdAt }]
52
+ }
53
+
54
+ export function activeChatsByAgentMap(record) {
55
+ if (record.deletedAt) return []
56
+ return [{ agentId: record.agentId, createdAt: record.createdAt }]
57
+ }
58
+
59
+ export function activeKnowledgeByChatMap(record) {
60
+ if (record.deletedAt || !record.chatId) return []
61
+ return [{ chatId: record.chatId }]
62
+ }
63
+
64
+ export function activeToolConfigsByAgentMap(record) {
65
+ if (record.deletedAt) return []
66
+ return [{ agentId: record.agentId }]
67
+ }
68
+
69
+ // append-only tool-call log keyed by agent then apply-assigned seq — one range query lists an
70
+ // agent's whole activity across every chat and device, in a peer-consistent order (seq, not wall
71
+ // clock). No tombstone filter: activity is immutable history.
72
+ export function toolActivityByAgentMap(record) {
73
+ if (!record.agentId) return []
74
+ return [{ agentId: record.agentId, seq: record.seq }]
75
+ }
76
+
77
+ export function runRequestsMap(record) {
78
+ if (record.type !== 'run-request') return []
79
+ return [{ chatId: record.chatId, seq: record.seq }]
80
+ }
81
+
82
+ // media kinds for the attachments-by-media-kind index. The ordinal rides in the key as a plain
83
+ // uint (same pattern as chunksByTypeMap); MEDIA_KINDS is the name↔ordinal source of truth, and
84
+ // the wire media-kind enum in engine.ts is registered from this same array so the two never drift.
85
+ export const MEDIA_KINDS = ['audio', 'image', 'video', 'other']
86
+
87
+ // `.mp4` is deliberately audio, not video: audio recordings ship in the mp4 container and are
88
+ // transcribed from their audio track (parity with isAudioAttachment). audio-attachment.ts derives
89
+ // isAudioAttachment from mediaKindOf, so this list is the one source of truth for "what is audio".
90
+ export const AUDIO_EXTENSIONS = ['.m4a', '.mp3', '.wav', '.ogg', '.flac', '.aac', '.mp4']
91
+ const IMAGE_EXTENSIONS = [
92
+ '.png',
93
+ '.jpg',
94
+ '.jpeg',
95
+ '.gif',
96
+ '.webp',
97
+ '.bmp',
98
+ '.svg',
99
+ '.heic',
100
+ '.heif',
101
+ '.avif'
102
+ ]
103
+ const VIDEO_EXTENSIONS = ['.mov', '.webm', '.mkv', '.avi', '.m4v']
104
+
105
+ function endsWithAny(name, extensions) {
106
+ return extensions.some((extension) => name.endsWith(extension))
107
+ }
108
+
109
+ // classify an attachment by mime first, then filename extension. Broad by design so a generated
110
+ // `audio/wav` and a user `recording.m4a` both land under 'audio'.
111
+ export function mediaKindOf(mimeType, fileName) {
112
+ const mime = (mimeType || '').toLowerCase()
113
+ const name = (fileName || '').toLowerCase()
114
+ if (mime.startsWith('audio/') || endsWithAny(name, AUDIO_EXTENSIONS)) return 'audio'
115
+ if (mime.startsWith('image/') || endsWithAny(name, IMAGE_EXTENSIONS)) return 'image'
116
+ if (mime.startsWith('video/') || endsWithAny(name, VIDEO_EXTENSIONS)) return 'video'
117
+ return 'other'
118
+ }
119
+
120
+ export function mediaKindOrdinal(kind) {
121
+ const ordinal = MEDIA_KINDS.indexOf(kind)
122
+ if (ordinal === -1) throw new Error('Unknown media kind: ' + kind)
123
+ return ordinal + 1
124
+ }
125
+
126
+ // every non-deleted attachment chunk, keyed by media kind then createdAt — NOT chatId-prefixed,
127
+ // so one range query lists all audio (uploaded or generated) across every chat in the mesh. Drops
128
+ // non-attachment chunks and tombstoned attachments so a gallery never scans-and-filters.
129
+ export function attachmentsByMediaKindMap(record) {
130
+ if (record.type !== 'attachment') return []
131
+ const attachment = record.attachment
132
+ if (!attachment || attachment.deletedAt) return []
133
+ return [
134
+ {
135
+ mediaKind: mediaKindOrdinal(mediaKindOf(attachment.mimeType, attachment.fileName)),
136
+ createdAt: record.createdAt
137
+ }
138
+ ]
139
+ }
140
+
141
+ // generated tool output always carries a runId; a user upload never does (attachment-knowledge.ts
142
+ // only routes runId-less uploads to knowledge). So runId presence IS the origin, kept out of the
143
+ // key as a plain 0/1 uint the way mediaKind is.
144
+ function attachmentGenerated(record) {
145
+ return record.runId ? 1 : 0
146
+ }
147
+
148
+ // the same attachments as attachmentsByMediaKindMap, but with origin (generated vs uploaded) folded
149
+ // into the key so "only generated audio" / "only uploaded audio" is a range query, not a post-filter.
150
+ export function attachmentsByMediaKindOriginMap(record) {
151
+ if (record.type !== 'attachment') return []
152
+ const attachment = record.attachment
153
+ if (!attachment || attachment.deletedAt) return []
154
+ return [
155
+ {
156
+ mediaKind: mediaKindOrdinal(mediaKindOf(attachment.mimeType, attachment.fileName)),
157
+ generated: attachmentGenerated(record),
158
+ createdAt: record.createdAt
159
+ }
160
+ ]
161
+ }
162
+
163
+ export function devicesByWriterKeyMap(record) {
164
+ if (!record.writerKey) return []
165
+ return [{ writerKey: record.writerKey }]
166
+ }
167
+
168
+ export function historyChunksMap(record) {
169
+ if (!historyChunkTypes.includes(record.type)) return []
170
+ return [{ chatId: record.chatId, seq: record.seq }]
171
+ }
172
+
173
+ export function liveWritersByMostRecentMap(record) {
174
+ if (record.revoked) return []
175
+ return [{ clock: record.clock }]
176
+ }
@@ -0,0 +1,5 @@
1
+ // these builder packages ship no types — blank ambient modules, schema-authoring-time only
2
+ declare module 'hrpc'
3
+ declare module 'hyperdispatch'
4
+ declare module 'hyperschema'
5
+ declare module 'hyperdb/builder'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qvac/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "Peer-to-peer AI across your devices",
6
6
  "exports": {
@@ -69,6 +69,10 @@
69
69
  "default": "./dist-lib/client.js"
70
70
  },
71
71
  "./capabilities": "./dist-lib/spec/capabilities.d.ts",
72
+ "./wire-codec": {
73
+ "types": "./dist-lib/spec/hrpc/messages.d.ts",
74
+ "default": "./dist-lib/spec/hrpc/messages.js"
75
+ },
72
76
  "./log": {
73
77
  "types": "./dist-lib/lib/log.d.ts",
74
78
  "default": "./dist-lib/lib/log.js"