@aopslabs/aops-server 0.2.15 → 0.2.17

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.
Files changed (30) hide show
  1. package/THIRD_PARTY_NOTICES +102 -28
  2. package/THIRD_PARTY_NOTICES.inventory.json +54 -44
  3. package/build/client/_app/immutable/chunks/{DSpwhhgJ.js → DsVD5sY-.js} +1 -1
  4. package/build/client/_app/immutable/entry/{app.DekpYahB.js → app.drhGhM7-.js} +2 -2
  5. package/build/client/_app/immutable/entry/start.DFlIg1ZE.js +1 -0
  6. package/build/client/_app/immutable/nodes/{1.C_m2PfHM.js → 1.CmvZWhgC.js} +1 -1
  7. package/build/client/_app/version.json +1 -1
  8. package/build/handler.js +4 -4
  9. package/build/index.js +4 -4
  10. package/build/server/chunks/chunks/{internal.js-DfNDnSaz.js → internal.js-CHt2o7ou.js} +1 -1
  11. package/build/server/chunks/{handler-BJwWMzoK.js → handler-BfsqMBoK.js} +2 -2
  12. package/build/server/chunks/{index.js-lf8q_rBQ.js → index.js-B85XMLmi.js} +1 -1
  13. package/build/server/chunks/{manifest.js-BbSaGAoy.js → manifest.js-BeFELu1I.js} +2 -2
  14. package/build/server/chunks/nodes/{1.js-DzchJAL9.js → 1.js-C7id1cFV.js} +1 -1
  15. package/cockpit/.vite/manifest.json +2 -2
  16. package/cockpit/assets/{index-B2e2Kw5U.css → index-BSNHteK1.css} +1 -1
  17. package/cockpit/assets/index-BUaKAOXP.js +18 -0
  18. package/cockpit/community.module-inventory.json +209 -189
  19. package/cockpit/index.html +2 -2
  20. package/migrations/docman/0007_text_file_profile.sql +15 -0
  21. package/migrations/docman/meta/_journal.json +7 -0
  22. package/npm-shrinkwrap.json +65 -58
  23. package/package.json +10 -9
  24. package/runtime/docman-policy.mjs +1 -0
  25. package/runtime-closure.package.json +23 -21
  26. package/scripts/community-agent-wake-endpoint.mjs +329 -0
  27. package/scripts/community-host.mjs +185 -1
  28. package/scripts/community-migration-policy-package-v1.json +44 -6
  29. package/build/client/_app/immutable/entry/start.DBrQBcQx.js +0 -1
  30. package/cockpit/assets/index-DjTrHh8F.js +0 -18
@@ -0,0 +1,329 @@
1
+ import { randomBytes } from 'node:crypto'
2
+ import {
3
+ AGENT_RUNTIME_WAKE_MAX_PROMPT_BYTES,
4
+ resolveAgentWakeTarget,
5
+ } from '@aopslabs/aops-agent-runtime'
6
+
7
+ export const COMMUNITY_AGENT_WAKE_CAPABILITY_PATH = '/_aops/agent-wake/capability'
8
+ export const COMMUNITY_AGENT_WAKE_ACTION_PATH = '/_aops/agent-wake'
9
+ export const COMMUNITY_AGENT_WAKE_GRANT_HEADER = 'x-aops-agent-wake-grant'
10
+
11
+ const MAX_BODY_BYTES = AGENT_RUNTIME_WAKE_MAX_PROMPT_BYTES + 16_384
12
+ export const COMMUNITY_AGENT_WAKE_PROBE_TIMEOUT_MS = 1_500
13
+ export const COMMUNITY_AGENT_WAKE_GRANT_TTL_MS = 30_000
14
+ const MAX_LIVE_GRANTS = 256
15
+
16
+ function jsonResponse(res, status, payload, securityHeaders = {}) {
17
+ res.writeHead(status, {
18
+ ...securityHeaders,
19
+ 'content-type': 'application/json; charset=utf-8',
20
+ 'cache-control': 'no-store',
21
+ })
22
+ res.end(`${JSON.stringify(payload)}\n`)
23
+ }
24
+
25
+ function token() {
26
+ return randomBytes(32).toString('base64url')
27
+ }
28
+
29
+ export function createCommunityAgentWakeContext({
30
+ wake,
31
+ verifyAdmission,
32
+ registryPath,
33
+ securityHeaders = {},
34
+ now = Date.now,
35
+ grantTtlMs = COMMUNITY_AGENT_WAKE_GRANT_TTL_MS,
36
+ }) {
37
+ if (typeof wake !== 'function') throw new Error('community_agent_wake_adapter_required')
38
+ if (typeof verifyAdmission !== 'function') throw new Error('community_agent_wake_admission_verifier_required')
39
+ if (typeof now !== 'function') throw new Error('community_agent_wake_clock_required')
40
+ if (!Number.isInteger(grantTtlMs) || grantTtlMs < 1 || grantTtlMs > 60_000) {
41
+ throw new Error('community_agent_wake_grant_ttl_invalid')
42
+ }
43
+ return Object.freeze({
44
+ wake,
45
+ verifyAdmission,
46
+ registryPath,
47
+ securityHeaders,
48
+ now,
49
+ grantTtlMs,
50
+ grants: new Map(),
51
+ })
52
+ }
53
+
54
+ function admissionFrom(input) {
55
+ return {
56
+ channelId: typeof input.channelId === 'string' ? input.channelId.trim() : '',
57
+ memberId: typeof input.memberId === 'string' ? input.memberId.trim() : '',
58
+ handle: typeof input.handle === 'string' ? input.handle.trim() : '',
59
+ actorKind: typeof input.actorKind === 'string' ? input.actorKind.trim().toLowerCase() : '',
60
+ status: typeof input.status === 'string' ? input.status.trim().toLowerCase() : '',
61
+ }
62
+ }
63
+
64
+ async function inspectCapability(admission, context) {
65
+ if (admission.actorKind !== 'agent') return { enabled: false, reason: 'not_agent', hostMatched: false }
66
+ if (admission.status !== 'active') return { enabled: false, reason: 'member_inactive', hostMatched: false }
67
+ let resolved
68
+ try {
69
+ resolved = resolveAgentWakeTarget(admission.channelId, admission.memberId, context.registryPath)
70
+ } catch (error) {
71
+ const code = error instanceof Error ? error.message : String(error)
72
+ return { enabled: false, reason: code.startsWith('agent_wake_registry_') ? code : 'invalid_target', hostMatched: false }
73
+ }
74
+ if (!resolved.entry) return { enabled: false, reason: 'not_registered', hostMatched: false }
75
+ if (resolved.entry.handle && resolved.entry.handle !== admission.handle) {
76
+ return { enabled: false, reason: 'identity_mismatch', hostMatched: resolved.hostMatched }
77
+ }
78
+ if (!resolved.hostMatched) {
79
+ return {
80
+ enabled: false,
81
+ reason: 'host_mismatch',
82
+ hostMatched: false,
83
+ runtime: resolved.entry.runtime,
84
+ targetSessionIdHash: resolved.entry.targetSessionIdHash,
85
+ lastVerifiedAt: resolved.entry.lastVerifiedAt,
86
+ }
87
+ }
88
+ const probe = await context.wake({
89
+ runtime: resolved.entry.runtime,
90
+ transport: 'desktop-ipc',
91
+ sessionId: resolved.entry.targetSessionId,
92
+ prompt: '',
93
+ dryRun: true,
94
+ handshakeTimeoutMs: COMMUNITY_AGENT_WAKE_PROBE_TIMEOUT_MS,
95
+ })
96
+ if (!probe?.envelope?.ok) {
97
+ return {
98
+ enabled: false,
99
+ reason: `dry_run_failed:${probe?.envelope?.code ?? 'unknown'}`,
100
+ hostMatched: true,
101
+ runtime: resolved.entry.runtime,
102
+ targetSessionIdHash: resolved.entry.targetSessionIdHash,
103
+ lastVerifiedAt: resolved.entry.lastVerifiedAt,
104
+ }
105
+ }
106
+ return {
107
+ enabled: true,
108
+ reason: 'ready',
109
+ hostMatched: true,
110
+ runtime: resolved.entry.runtime,
111
+ targetSessionIdHash: resolved.entry.targetSessionIdHash,
112
+ lastVerifiedAt: resolved.entry.lastVerifiedAt,
113
+ entry: resolved.entry,
114
+ }
115
+ }
116
+
117
+ async function readJsonBody(req) {
118
+ const contentType = String(req.headers['content-type'] ?? '').split(';', 1)[0].trim().toLowerCase()
119
+ if (contentType !== 'application/json') throw new Error('content_type_json_required')
120
+ const contentLength = Number(req.headers['content-length'] ?? 0)
121
+ if (Number.isFinite(contentLength) && contentLength > MAX_BODY_BYTES) throw new Error('request_body_too_large')
122
+ const chunks = []
123
+ let bytes = 0
124
+ for await (const chunk of req) {
125
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
126
+ bytes += buffer.length
127
+ if (bytes > MAX_BODY_BYTES) throw new Error('request_body_too_large')
128
+ chunks.push(buffer)
129
+ }
130
+ const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'))
131
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('request_body_invalid')
132
+ return parsed
133
+ }
134
+
135
+ function publicCapability(capability) {
136
+ const { entry: _entry, ...safe } = capability
137
+ return safe
138
+ }
139
+
140
+ function admissionKey(admission) {
141
+ return [
142
+ admission.channelId,
143
+ admission.memberId,
144
+ admission.handle,
145
+ admission.actorKind,
146
+ admission.status,
147
+ ].join('\u0000')
148
+ }
149
+
150
+ function cleanupExpiredGrants(context) {
151
+ const now = context.now()
152
+ for (const [grantToken, grant] of context.grants) {
153
+ if (grant.expiresAt <= now) context.grants.delete(grantToken)
154
+ }
155
+ while (context.grants.size >= MAX_LIVE_GRANTS) {
156
+ const oldest = context.grants.keys().next().value
157
+ if (!oldest) break
158
+ context.grants.delete(oldest)
159
+ }
160
+ }
161
+
162
+ function issueGrant(admission, verifiedCaller, context) {
163
+ cleanupExpiredGrants(context)
164
+ const value = token()
165
+ const expiresAt = context.now() + context.grantTtlMs
166
+ context.grants.set(value, {
167
+ principalId: verifiedCaller.principalId,
168
+ callerMemberId: verifiedCaller.callerMemberId,
169
+ callerChannelId: verifiedCaller.callerChannelId,
170
+ admissionKey: admissionKey(admission),
171
+ expiresAt,
172
+ })
173
+ return { value, expiresAt }
174
+ }
175
+
176
+ function consumeGrant(req, admission, verifiedCaller, context) {
177
+ const value = req.headers[COMMUNITY_AGENT_WAKE_GRANT_HEADER]
178
+ if (typeof value !== 'string' || !value) return 'agent_wake_grant_invalid'
179
+ const grant = context.grants.get(value)
180
+ if (!grant) return 'agent_wake_grant_invalid'
181
+ context.grants.delete(value)
182
+ if (grant.expiresAt <= context.now()) return 'agent_wake_grant_expired'
183
+ if (grant.principalId !== verifiedCaller.principalId) return 'agent_wake_grant_principal_mismatch'
184
+ if (
185
+ grant.callerMemberId !== verifiedCaller.callerMemberId
186
+ || grant.callerChannelId !== verifiedCaller.callerChannelId
187
+ ) return 'agent_wake_grant_caller_mismatch'
188
+ if (grant.admissionKey !== admissionKey(admission)) return 'agent_wake_grant_scope_mismatch'
189
+ return null
190
+ }
191
+
192
+ const SAFE_ADMISSION_ERRORS = new Set([
193
+ 'agent_wake_member_token_required',
194
+ 'agent_wake_member_token_invalid_format',
195
+ 'agent_wake_member_token_unknown',
196
+ 'agent_wake_member_token_mismatch',
197
+ 'agent_wake_member_not_active',
198
+ 'agent_wake_member_cross_channel',
199
+ 'agent_wake_target_not_active',
200
+ 'agent_wake_admission_required',
201
+ ])
202
+
203
+ function verifiedTargetAdmission(selector, verified) {
204
+ const member = verified?.targetMember
205
+ const memberId = typeof member?.id === 'string' ? member.id.trim() : ''
206
+ const channelId = typeof member?.channelId === 'string' ? member.channelId.trim() : ''
207
+ if (!memberId || memberId !== selector.memberId || channelId !== selector.channelId) {
208
+ throw new Error('agent_wake_target_not_active')
209
+ }
210
+ return admissionFrom({
211
+ channelId,
212
+ memberId,
213
+ handle: member.handle,
214
+ actorKind: member.actorKind,
215
+ status: member.status,
216
+ })
217
+ }
218
+
219
+ async function verifiedMemberAdmission(req, browser, selector, context) {
220
+ if (!browser.browserOriginPresent) return { error: 'same_origin_required' }
221
+ try {
222
+ const verified = await context.verifyAdmission(req, {
223
+ channelId: selector.channelId,
224
+ targetMemberId: selector.memberId,
225
+ })
226
+ const principalId = typeof verified?.principalId === 'string' ? verified.principalId.trim() : ''
227
+ const callerMemberId = typeof verified?.callerMemberId === 'string' ? verified.callerMemberId.trim() : ''
228
+ const callerChannelId = typeof verified?.callerChannelId === 'string' ? verified.callerChannelId.trim() : ''
229
+ if (!principalId || !callerMemberId || !callerChannelId) return { error: 'agent_wake_admission_required' }
230
+ if (callerChannelId !== selector.channelId) return { error: 'agent_wake_member_cross_channel' }
231
+ return {
232
+ principalId,
233
+ callerMemberId,
234
+ callerChannelId,
235
+ targetAdmission: verifiedTargetAdmission(selector, verified),
236
+ }
237
+ } catch (error) {
238
+ const code = error instanceof Error ? error.message : ''
239
+ return { error: SAFE_ADMISSION_ERRORS.has(code) ? code : 'agent_wake_admission_required' }
240
+ }
241
+ }
242
+
243
+ export async function handleCommunityAgentWakeRequest(req, res, url, browser, context) {
244
+ if (url.pathname !== COMMUNITY_AGENT_WAKE_CAPABILITY_PATH && url.pathname !== COMMUNITY_AGENT_WAKE_ACTION_PATH) return false
245
+ if (url.pathname === COMMUNITY_AGENT_WAKE_CAPABILITY_PATH) {
246
+ if (req.method !== 'POST') {
247
+ res.writeHead(405, { allow: 'POST' })
248
+ res.end()
249
+ return true
250
+ }
251
+ let body
252
+ try {
253
+ body = await readJsonBody(req)
254
+ } catch (error) {
255
+ jsonResponse(res, 400, { ok: false, code: error instanceof Error ? error.message : 'request_body_invalid' }, context.securityHeaders)
256
+ return true
257
+ }
258
+ const selector = admissionFrom(body)
259
+ const verified = await verifiedMemberAdmission(req, browser, selector, context)
260
+ if (verified.error) {
261
+ jsonResponse(res, 403, { ok: false, code: verified.error }, context.securityHeaders)
262
+ return true
263
+ }
264
+ const admission = verified.targetAdmission
265
+ const capability = await inspectCapability(admission, context)
266
+ jsonResponse(res, 200, {
267
+ ok: true,
268
+ capability: publicCapability(capability),
269
+ grant: capability.enabled ? issueGrant(admission, verified, context) : null,
270
+ }, context.securityHeaders)
271
+ return true
272
+ }
273
+ if (req.method !== 'POST') {
274
+ res.writeHead(405, { allow: 'POST' })
275
+ res.end()
276
+ return true
277
+ }
278
+ let body
279
+ try {
280
+ body = await readJsonBody(req)
281
+ } catch (error) {
282
+ jsonResponse(res, 400, { ok: false, code: error instanceof Error ? error.message : 'request_body_invalid' }, context.securityHeaders)
283
+ return true
284
+ }
285
+ const prompt = typeof body.prompt === 'string' ? body.prompt : ''
286
+ if (!prompt.trim() || Buffer.byteLength(prompt, 'utf8') > AGENT_RUNTIME_WAKE_MAX_PROMPT_BYTES) {
287
+ jsonResponse(res, 400, { ok: false, code: 'invalid_prompt' }, context.securityHeaders)
288
+ return true
289
+ }
290
+ const selector = admissionFrom(body)
291
+ const verified = await verifiedMemberAdmission(req, browser, selector, context)
292
+ if (verified.error) {
293
+ jsonResponse(res, 403, { ok: false, code: verified.error }, context.securityHeaders)
294
+ return true
295
+ }
296
+ const admission = verified.targetAdmission
297
+ const grantError = consumeGrant(req, admission, verified, context)
298
+ if (grantError) {
299
+ jsonResponse(res, 403, { ok: false, code: grantError }, context.securityHeaders)
300
+ return true
301
+ }
302
+ const capability = await inspectCapability(admission, context)
303
+ if (!capability.enabled || !capability.entry) {
304
+ jsonResponse(res, 409, { ok: false, code: capability.reason, capability: publicCapability(capability) }, context.securityHeaders)
305
+ return true
306
+ }
307
+ const result = await context.wake({
308
+ runtime: capability.entry.runtime,
309
+ transport: 'desktop-ipc',
310
+ sessionId: capability.entry.targetSessionId,
311
+ prompt,
312
+ })
313
+ if (!result?.envelope?.ok) {
314
+ jsonResponse(res, result?.envelope?.retryable ? 503 : 409, {
315
+ ok: false,
316
+ code: result?.envelope?.code ?? 'wake_failed',
317
+ targetSessionIdHash: capability.entry.targetSessionIdHash,
318
+ }, context.securityHeaders)
319
+ return true
320
+ }
321
+ jsonResponse(res, 200, {
322
+ ok: true,
323
+ code: 'wake_sent',
324
+ runtime: capability.entry.runtime,
325
+ targetSessionIdHash: capability.entry.targetSessionIdHash,
326
+ turnId: result.envelope.turnId ?? null,
327
+ }, context.securityHeaders)
328
+ return true
329
+ }
@@ -12,6 +12,17 @@ import { createServer, request as httpRequest } from "node:http";
12
12
  import { isIP } from "node:net";
13
13
  import path from "node:path";
14
14
  import { pathToFileURL } from "node:url";
15
+ import { wakeAgentSession as wakeLocalAgentSession } from "@aopslabs/aops-agent-runtime";
16
+ import {
17
+ COMMUNITY_AGENT_WAKE_ACTION_PATH,
18
+ COMMUNITY_AGENT_WAKE_CAPABILITY_PATH,
19
+ createCommunityAgentWakeContext,
20
+ handleCommunityAgentWakeRequest,
21
+ } from "./community-agent-wake-endpoint.mjs";
22
+
23
+ // Imported by the host process (never from the browser bundle or CLI package).
24
+ // S3 binds this runtime-keyed adapter to the authenticated loopback endpoint.
25
+ export const communityHostAgentWakeAdapter = wakeLocalAgentSession;
15
26
 
16
27
  export const COMMUNITY_HOST_MODES = Object.freeze({
17
28
  native: "direct-loopback",
@@ -29,6 +40,8 @@ const DEFAULT_OCI_INTERNAL_PORT = "5901";
29
40
  const DEFAULT_COCKPIT_PORT = "5922";
30
41
  const TRUSTED_LOCAL_AUTH_PROVIDER = "trusted-local";
31
42
  const VALIDATED_PUBLIC_ORIGIN_HEADER = "x-aops-validated-public-origin";
43
+ const AGENT_WAKE_ADMISSION_TIMEOUT_MS = 1_500;
44
+ const AGENT_WAKE_ADMISSION_MAX_BYTES = 1024 * 1024;
32
45
  const PACKAGE_STATIC_ROOT = path.resolve(import.meta.dirname, "../cockpit");
33
46
  const CHECKOUT_STATIC_ROOT = path.resolve(import.meta.dirname, "../../aops-cockpit-v2/dist");
34
47
  const DEFAULT_PATHS = Object.freeze({
@@ -624,6 +637,144 @@ export function proxyCommunityRequest(req, res, config, browserOriginPresent) {
624
637
  req.pipe(upstream);
625
638
  }
626
639
 
640
+ function requestCommunityAgentWakeAdmission(config, pathName, headers) {
641
+ return new Promise((resolve, reject) => {
642
+ const upstream = httpRequest({
643
+ hostname: config.internalHost,
644
+ port: config.internalPort,
645
+ method: "GET",
646
+ path: pathName,
647
+ headers,
648
+ }, (response) => {
649
+ const chunks = [];
650
+ let bytes = 0;
651
+ response.on("data", (chunk) => {
652
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
653
+ bytes += buffer.length;
654
+ if (bytes > AGENT_WAKE_ADMISSION_MAX_BYTES) {
655
+ upstream.destroy(new Error("community_agent_wake_admission_response_too_large"));
656
+ return;
657
+ }
658
+ chunks.push(buffer);
659
+ });
660
+ response.once("end", () => {
661
+ try {
662
+ const text = Buffer.concat(chunks).toString("utf8");
663
+ const payload = text ? JSON.parse(text) : null;
664
+ resolve({ status: response.statusCode ?? 500, payload });
665
+ } catch {
666
+ reject(new Error("community_agent_wake_admission_response_invalid"));
667
+ }
668
+ });
669
+ response.once("error", reject);
670
+ });
671
+ upstream.setTimeout(AGENT_WAKE_ADMISSION_TIMEOUT_MS, () => {
672
+ upstream.destroy(new Error("community_agent_wake_admission_timeout"));
673
+ });
674
+ upstream.once("error", reject);
675
+ upstream.end();
676
+ });
677
+ }
678
+
679
+ function memberAdmissionError(payload) {
680
+ const structuredReason = [
681
+ payload?.details?.memberAuthReason,
682
+ payload?.data?.details?.memberAuthReason,
683
+ payload?.error?.details?.memberAuthReason,
684
+ payload?.data?.error?.details?.memberAuthReason,
685
+ ].find((value) => typeof value === "string");
686
+ const structuredCodes = {
687
+ member_token_required: "agent_wake_member_token_required",
688
+ member_token_invalid_format: "agent_wake_member_token_invalid_format",
689
+ member_token_unknown: "agent_wake_member_token_unknown",
690
+ member_token_mismatch: "agent_wake_member_token_mismatch",
691
+ membership_inactive: "agent_wake_member_not_active",
692
+ membership_channel_mismatch: "agent_wake_member_cross_channel",
693
+ };
694
+ if (structuredReason && structuredCodes[structuredReason]) {
695
+ return structuredCodes[structuredReason];
696
+ }
697
+
698
+ // Mixed-version trusted-local fallback only. Current released ChatV3 hosts
699
+ // do not yet emit memberAuthReason; remove after the package uptake window.
700
+ const detail = [
701
+ payload?.message,
702
+ payload?.error,
703
+ payload?.detail,
704
+ payload?.data?.message,
705
+ payload?.data?.error,
706
+ ].filter((value) => typeof value === "string").join(" ").toLowerCase();
707
+ if (detail.includes("invalid member token format")) return "agent_wake_member_token_invalid_format";
708
+ if (detail.includes("unknown member token")) return "agent_wake_member_token_unknown";
709
+ if (detail.includes("member token mismatch")) return "agent_wake_member_token_mismatch";
710
+ if (detail.includes("membership is not active")) return "agent_wake_member_not_active";
711
+ if (detail.includes("not a member of this channel")) return "agent_wake_member_cross_channel";
712
+ return "agent_wake_admission_required";
713
+ }
714
+
715
+ function responseData(payload) {
716
+ if (payload?.ok === true && payload.data !== undefined) return payload.data;
717
+ if (payload?.result?.data !== undefined) return payload.result.data;
718
+ return payload;
719
+ }
720
+
721
+ function parseVerifiedMemberTokenId(memberToken) {
722
+ const match = /^cv3m_([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})_[A-Za-z0-9_-]{16,}$/i.exec(memberToken);
723
+ return match?.[1] ?? null;
724
+ }
725
+
726
+ export async function verifyCommunityAgentWakeAdmission(req, config, selector = {}) {
727
+ const headers = {
728
+ accept: "application/json",
729
+ host: config.upstreamAuthority,
730
+ };
731
+ const cookie = typeof req.headers.cookie === "string" ? req.headers.cookie.trim() : "";
732
+ if (cookie) headers.cookie = cookie;
733
+ if (typeof req.headers.origin === "string" && req.headers.origin.trim()) {
734
+ headers.origin = config.upstreamOrigin;
735
+ }
736
+ const auth = await requestCommunityAgentWakeAdmission(config, "/api/auth/me", headers);
737
+ if (auth.status !== 200) throw new Error("agent_wake_admission_required");
738
+ const principal = auth.payload?.ok === true ? auth.payload?.data?.principal : null;
739
+ const principalId = nonEmpty(principal?.id) || nonEmpty(principal?.userId);
740
+ if (!principalId) throw new Error("agent_wake_admission_required");
741
+
742
+ const memberTokenHeader = typeof req.headers["x-chatv3-member-token"] === "string"
743
+ ? req.headers["x-chatv3-member-token"].trim()
744
+ : "";
745
+ const memberToken = /^Bearer\s+/i.test(memberTokenHeader)
746
+ ? memberTokenHeader.replace(/^Bearer\s+/i, "").trim()
747
+ : memberTokenHeader;
748
+ if (!memberToken) throw new Error("agent_wake_member_token_required");
749
+ const channelId = nonEmpty(selector.channelId);
750
+ const targetMemberId = nonEmpty(selector.targetMemberId);
751
+ if (!channelId || !targetMemberId) throw new Error("agent_wake_target_not_active");
752
+ const roster = await requestCommunityAgentWakeAdmission(
753
+ config,
754
+ `/api/chatv3/v1/channels/${encodeURIComponent(channelId)}/members?status=active&limit=500`,
755
+ { ...headers, "x-chatv3-member-token": memberToken },
756
+ );
757
+ if (roster.status !== 200) throw new Error(memberAdmissionError(roster.payload));
758
+ const callerMemberId = parseVerifiedMemberTokenId(memberToken);
759
+ if (!callerMemberId) throw new Error("agent_wake_member_token_invalid_format");
760
+ const members = responseData(roster.payload);
761
+ if (!Array.isArray(members)) throw new Error("agent_wake_admission_required");
762
+ const callerMember = members.find((member) => member?.id === callerMemberId);
763
+ if (!callerMember || callerMember.channelId !== channelId || callerMember.status !== "active") {
764
+ throw new Error("agent_wake_member_not_active");
765
+ }
766
+ const targetMember = members.find((member) => member?.id === targetMemberId);
767
+ if (!targetMember || targetMember.channelId !== channelId || targetMember.status !== "active") {
768
+ throw new Error("agent_wake_target_not_active");
769
+ }
770
+ return {
771
+ principalId,
772
+ callerMemberId,
773
+ callerChannelId: channelId,
774
+ targetMember,
775
+ };
776
+ }
777
+
627
778
  function openStaticFile(filePath) {
628
779
  const flags = fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0);
629
780
  const descriptor = openSync(filePath, flags);
@@ -719,7 +870,7 @@ async function loadHandler(handlerEntry) {
719
870
  return loaded.handler;
720
871
  }
721
872
 
722
- export async function runCommunityHost(options, env = process.env, hostPaths = DEFAULT_PATHS) {
873
+ export async function runCommunityHost(options, env = process.env, hostPaths = DEFAULT_PATHS, hostRuntime = {}) {
723
874
  const config = resolveCommunityHostConfig(options, env);
724
875
  const cockpitOnly = config.mode === COMMUNITY_HOST_MODES.cockpit;
725
876
  if (!cockpitOnly && env !== process.env) throw new Error("community_host_process_env_required");
@@ -734,6 +885,20 @@ export async function runCommunityHost(options, env = process.env, hostPaths = D
734
885
  process.env.ORIGIN = config.handlerOrigin ?? config.publicOrigin;
735
886
  }
736
887
  const handler = cockpitOnly ? null : await loadHandler(paths.handlerEntry);
888
+ const agentWakeContext = cockpitOnly
889
+ ? createCommunityAgentWakeContext({
890
+ wake: hostRuntime.wakeAgentSession ?? communityHostAgentWakeAdapter,
891
+ verifyAdmission: (req, selector) => (
892
+ hostRuntime.verifyAgentWakeAdmission
893
+ ? hostRuntime.verifyAgentWakeAdmission(req, config, selector)
894
+ : verifyCommunityAgentWakeAdmission(req, config, selector)
895
+ ),
896
+ registryPath: nonEmpty(env.AOPS_AGENT_WAKE_REGISTRY_PATH) || undefined,
897
+ securityHeaders: STATIC_SECURITY_HEADERS,
898
+ now: hostRuntime.agentWakeNow ?? Date.now,
899
+ grantTtlMs: hostRuntime.agentWakeGrantTtlMs,
900
+ })
901
+ : null;
737
902
  let innerServer;
738
903
  let innerSockets = new Set();
739
904
  let edgeServer;
@@ -802,6 +967,25 @@ export async function runCommunityHost(options, env = process.env, hostPaths = D
802
967
  res.end(req.method === "HEAD" ? undefined : '{"status":"healthy","service":"aops-cockpit"}');
803
968
  return;
804
969
  }
970
+ if (
971
+ cockpitOnly &&
972
+ agentWakeContext &&
973
+ (url.pathname === COMMUNITY_AGENT_WAKE_CAPABILITY_PATH || url.pathname === COMMUNITY_AGENT_WAKE_ACTION_PATH)
974
+ ) {
975
+ void handleCommunityAgentWakeRequest(req, res, url, browser, agentWakeContext).catch(() => {
976
+ if (!res.headersSent) {
977
+ res.writeHead(500, {
978
+ ...STATIC_SECURITY_HEADERS,
979
+ "content-type": "application/json; charset=utf-8",
980
+ "cache-control": "no-store",
981
+ });
982
+ res.end('{"ok":false,"code":"agent_wake_internal_error"}');
983
+ } else {
984
+ res.destroy();
985
+ }
986
+ });
987
+ return;
988
+ }
805
989
  if (
806
990
  !shouldHandleApiPath(url.pathname) &&
807
991
  (config.mode === COMMUNITY_HOST_MODES.oci || cockpitOnly)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "id": "aops-community-strict-migration-v5",
3
+ "id": "aops-community-strict-migration-v6",
4
4
  "inventorySha256": "3ca2e22e8af86db2be8e7eccb868d5d5858b75d67f53080ddf52496853de9c40",
5
5
  "sourceArtifacts": {
6
6
  "convergenceFileSha256": "0e4e54109ec57f03e278751e29850ebd17fa5e9cc9df6ce8de6c767769714979",
@@ -85,9 +85,10 @@
85
85
  "migrationsDir": "migrations/docman",
86
86
  "migrationTable": "docman_schema_migrations",
87
87
  "legacyHashColumn": false,
88
- "journalSha256": "6c9efe998ae606a4a31f0e4f5ba7726acd066e0dd10795fbda7c2768328ebfb4",
88
+ "journalSha256": "eff4f39410b10be3ecf1b708a5f17d974f8d6fbbc102487e779d24f5ccbf9bcd",
89
89
  "journalSha256History": [
90
- "6c9efe998ae606a4a31f0e4f5ba7726acd066e0dd10795fbda7c2768328ebfb4"
90
+ "6c9efe998ae606a4a31f0e4f5ba7726acd066e0dd10795fbda7c2768328ebfb4",
91
+ "eff4f39410b10be3ecf1b708a5f17d974f8d6fbbc102487e779d24f5ccbf9bcd"
91
92
  ],
92
93
  "migrations": [
93
94
  {
@@ -131,6 +132,12 @@
131
132
  "tag": "0006_document_version_current_invariant",
132
133
  "sha256": "96fcf7eed81e6f10a3986ed0881e98597747587b02ee8590f8bdcf2da0cd54a9",
133
134
  "risk": "additive"
135
+ },
136
+ {
137
+ "idx": 7,
138
+ "tag": "0007_text_file_profile",
139
+ "sha256": "b4e352063300e19576aa83a62b087e09cb1792b32163481ba0b86b76e27a2f84",
140
+ "risk": "additive"
134
141
  }
135
142
  ]
136
143
  },
@@ -372,6 +379,23 @@
372
379
  "policyId": "aops-community-strict-migration-v5",
373
380
  "inventorySha256": "3ca2e22e8af86db2be8e7eccb868d5d5858b75d67f53080ddf52496853de9c40",
374
381
  "convergenceSha256": "dcc49a31f8fecaba7d3f05c939a78f266ca7543d46da62ef9c3ac2176df024b2"
382
+ },
383
+ {
384
+ "id": "strict-v5",
385
+ "kind": "strict",
386
+ "relationCount": 86,
387
+ "schemaFingerprintSha256": "92909ca23489c9d4cefba3ff3c55c10afe9cd587ad7a6a244810c7508ddec07d",
388
+ "appliedCounts": [
389
+ 6,
390
+ 1,
391
+ 8,
392
+ 6,
393
+ 8
394
+ ],
395
+ "postgresMajor": 16,
396
+ "policyId": "aops-community-strict-migration-v6",
397
+ "inventorySha256": "3ca2e22e8af86db2be8e7eccb868d5d5858b75d67f53080ddf52496853de9c40",
398
+ "convergenceSha256": "dcc49a31f8fecaba7d3f05c939a78f266ca7543d46da62ef9c3ac2176df024b2"
375
399
  }
376
400
  ],
377
401
  "lineageReconciliations": [
@@ -397,10 +421,10 @@
397
421
  ]
398
422
  }
399
423
  ],
400
- "targetLineageId": "strict-v4",
424
+ "targetLineageId": "strict-v5",
401
425
  "semanticContract": {
402
426
  "schemaVersion": 1,
403
- "migrationSetSha256": "6fcbcd576cd40c7175d101f8c29dcf0df498bd03eb0654689b6e27e559e362eb",
427
+ "migrationSetSha256": "443ce196a1954a73c01bcaff5092addae28b34b96c6ecf6e7bf4e77d2570d29a",
404
428
  "relations": [
405
429
  {
406
430
  "kind": "r",
@@ -1428,6 +1452,13 @@
1428
1452
  "table": "docman_page_versions",
1429
1453
  "type": "jsonb"
1430
1454
  },
1455
+ {
1456
+ "column": "contentMode",
1457
+ "defaultExpression": "'structured'::text",
1458
+ "notNull": true,
1459
+ "table": "docman_document_versions",
1460
+ "type": "text"
1461
+ },
1431
1462
  {
1432
1463
  "column": "costUsd",
1433
1464
  "defaultExpression": null,
@@ -2961,6 +2992,13 @@
2961
2992
  "table": "docman_document_index_entries",
2962
2993
  "type": "text"
2963
2994
  },
2995
+ {
2996
+ "column": "fileExtension",
2997
+ "defaultExpression": "'md'::text",
2998
+ "notNull": true,
2999
+ "table": "docman_document_versions",
3000
+ "type": "text"
3001
+ },
2964
3002
  {
2965
3003
  "column": "filename",
2966
3004
  "defaultExpression": null,
@@ -12008,6 +12046,6 @@
12008
12046
  "table": "workflow-step-runs"
12009
12047
  }
12010
12048
  ],
12011
- "fingerprintSha256": "aa7420f6d7a53f009a3d4a6280bc1660e043c9a5eeac02444b02c707f7361d01"
12049
+ "fingerprintSha256": "167d61e2311d5d7d8e2bb192ec80daa38e8516440775bf56c21cbd1185348c48"
12012
12050
  }
12013
12051
  }
@@ -1 +0,0 @@
1
- import{l as o,a as r}from"../chunks/DSpwhhgJ.js";export{o as load_css,r as start};