@bbigbang/core 0.1.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/dist/config.js +380 -0
- package/dist/execution/executionDispatcher.js +3810 -0
- package/dist/main.js +90 -0
- package/dist/nodeEventHistory.js +206 -0
- package/dist/scheduler/dreamLogic.js +50 -0
- package/dist/scheduler/dreamScheduler.js +65 -0
- package/dist/services/agentFileAccessService.js +1913 -0
- package/dist/services/agentRuntimeCleanupBroker.js +62 -0
- package/dist/services/agentSkillsBroker.js +118 -0
- package/dist/services/agentSkillsService.js +83 -0
- package/dist/services/agentWorkspaceBroker.js +937 -0
- package/dist/services/agentWorkspaceService.js +70 -0
- package/dist/services/appVersion.js +14 -0
- package/dist/services/auth.js +586 -0
- package/dist/services/claudeControlBroker.js +154 -0
- package/dist/services/claudeTranscriptBroker.js +100 -0
- package/dist/services/claudeTranscriptService.js +359 -0
- package/dist/services/codexAppServerBroker.js +155 -0
- package/dist/services/codexTranscriptBroker.js +98 -0
- package/dist/services/codexTranscriptService.js +961 -0
- package/dist/services/droidMissionBroker.js +124 -0
- package/dist/services/droidMissionImporter.js +630 -0
- package/dist/services/droidModelOptions.js +165 -0
- package/dist/services/hubServerRegistrationService.js +268 -0
- package/dist/services/libraryManifest.js +43 -0
- package/dist/services/libraryScaffold.js +26 -0
- package/dist/services/libraryService.js +2263 -0
- package/dist/services/memoryService.js +386 -0
- package/dist/services/missionEvidence.js +377 -0
- package/dist/services/missionService.js +2361 -0
- package/dist/services/missionTrace.js +158 -0
- package/dist/services/nativeMissionBriefParser.js +120 -0
- package/dist/services/nativeMissionOrchestrator.js +2045 -0
- package/dist/services/nativeMissionReportGenerator.js +227 -0
- package/dist/services/nativeMissionValidationRunner.js +452 -0
- package/dist/services/nativeMissionWorkerBroker.js +190 -0
- package/dist/services/nodeRegistry.js +34 -0
- package/dist/services/nodeStateReconciler.js +97 -0
- package/dist/services/panelMediaScanner.js +119 -0
- package/dist/services/persistentRuntimeJsonlClient.js +153 -0
- package/dist/services/platformAgentPolicy.js +180 -0
- package/dist/services/platformAgentService.js +2041 -0
- package/dist/services/projectAccessResolver.js +93 -0
- package/dist/services/projectService.js +392 -0
- package/dist/services/resourceSpaceService.js +140 -0
- package/dist/services/scenarioRuntimeService.js +1130 -0
- package/dist/services/suggestedPlannerService.js +868 -0
- package/dist/services/workbenchGitBroker.js +161 -0
- package/dist/services/workbenchGitService.js +69 -0
- package/dist/services/workbenchInspectBroker.js +65 -0
- package/dist/services/workbenchNodePathService.js +79 -0
- package/dist/services/workbenchRegistryService.js +240 -0
- package/dist/services/workbenchRootService.js +181 -0
- package/dist/services/workbenchTerminalBroker.js +378 -0
- package/dist/services/workspaceRunOwnership.js +60 -0
- package/dist/services/workspaceScaffold.js +105 -0
- package/dist/services/workspaceSessionRuntimeService.js +576 -0
- package/dist/services/workspaceSessionService.js +245 -0
- package/dist/services/workspaceToolActionRunner.js +1582 -0
- package/dist/services/workspaceToolErrors.js +10 -0
- package/dist/services/workspaceToolExecutionUtils.js +895 -0
- package/dist/services/workspaceToolLatestStateProjector.js +91 -0
- package/dist/services/workspaceToolManifest.js +572 -0
- package/dist/services/workspaceToolMutationQueue.js +43 -0
- package/dist/services/workspaceToolPanelProjection.js +460 -0
- package/dist/services/workspaceToolPromotion.js +255 -0
- package/dist/services/workspaceToolPromotionState.js +224 -0
- package/dist/services/workspaceToolPublishDiagnostics.js +189 -0
- package/dist/services/workspaceToolPublishIdentityResolver.js +146 -0
- package/dist/services/workspaceToolReadModel.js +378 -0
- package/dist/services/workspaceToolRunLedger.js +239 -0
- package/dist/services/workspaceToolService.js +3067 -0
- package/dist/services/workspaceToolSnapshotPanelSync.js +293 -0
- package/dist/services/workspaceToolTerminalLifecycle.js +283 -0
- package/dist/services/workspaceToolTypes.js +1 -0
- package/dist/services/workspaceToolUploadMaterializer.js +228 -0
- package/dist/web/actionCardRoutes.js +129 -0
- package/dist/web/actionCards.js +469 -0
- package/dist/web/activationContext.js +684 -0
- package/dist/web/agentChannelGuards.js +48 -0
- package/dist/web/agentMentionCooldowns.js +32 -0
- package/dist/web/agentReminders.js +1668 -0
- package/dist/web/agentRuntimePresence.js +197 -0
- package/dist/web/agentSelfState.js +494 -0
- package/dist/web/agentTaskLinks.js +26 -0
- package/dist/web/agentVisibility.js +79 -0
- package/dist/web/assets.js +95 -0
- package/dist/web/channelActivationPrompt.js +395 -0
- package/dist/web/channelMemoryNotes.js +127 -0
- package/dist/web/channelMentions.js +10 -0
- package/dist/web/channelMessageSequences.js +19 -0
- package/dist/web/channelSubscriptions.js +26 -0
- package/dist/web/clearedTaskRoots.js +10 -0
- package/dist/web/collaborationPromptGuidance.js +36 -0
- package/dist/web/collaborationSurfaceState.js +140 -0
- package/dist/web/contextBundleRanking.js +154 -0
- package/dist/web/contextBundleResolver.js +488 -0
- package/dist/web/conversationBuiltinSkillRoots.js +50 -0
- package/dist/web/conversationControls.js +232 -0
- package/dist/web/conversationHandoffs.js +612 -0
- package/dist/web/conversationManager.js +2511 -0
- package/dist/web/conversationSummaries.js +876 -0
- package/dist/web/conversationSurfaceKinds.js +17 -0
- package/dist/web/conversationTargets.js +173 -0
- package/dist/web/directActivationPrompt.js +122 -0
- package/dist/web/directReplyTargets.js +69 -0
- package/dist/web/directThreadResolver.js +129 -0
- package/dist/web/dmTaskHandoffPrompt.js +120 -0
- package/dist/web/dmTaskThreadStatusProjection.js +229 -0
- package/dist/web/ftsQuery.js +33 -0
- package/dist/web/internalAgentRouter.js +11341 -0
- package/dist/web/libraryCuratorScheduler.js +58 -0
- package/dist/web/libraryDocumentPromptGuidance.js +8 -0
- package/dist/web/messageCheckpoints.js +19 -0
- package/dist/web/nodeWsHandler.js +2495 -0
- package/dist/web/notificationRounds.js +1061 -0
- package/dist/web/panelActionMessages.js +108 -0
- package/dist/web/panelActivationPrompt.js +18 -0
- package/dist/web/panelAudit.js +273 -0
- package/dist/web/panelLifecycle.js +222 -0
- package/dist/web/panelMediaPolicy.js +43 -0
- package/dist/web/panelPathPolicy.js +63 -0
- package/dist/web/panelPreviews.js +175 -0
- package/dist/web/panelQueryHandles.js +2749 -0
- package/dist/web/panelRoutes.js +2147 -0
- package/dist/web/panels.js +904 -0
- package/dist/web/peerInboxAggregates.js +1247 -0
- package/dist/web/planApprovalState.js +92 -0
- package/dist/web/platformAgentScheduler.js +66 -0
- package/dist/web/proactiveOpportunities.js +452 -0
- package/dist/web/promptContextSections.js +242 -0
- package/dist/web/promptHistorySanitizer.js +26 -0
- package/dist/web/promptSlashCommands.js +158 -0
- package/dist/web/rollingConversationSummary.js +453 -0
- package/dist/web/routeHelpers.js +11 -0
- package/dist/web/routes/handoff.js +288 -0
- package/dist/web/routes/history.js +345 -0
- package/dist/web/routes/memory.js +258 -0
- package/dist/web/routes/selfState.js +171 -0
- package/dist/web/routes/workspace.js +154 -0
- package/dist/web/runSurfaceWatermarks.js +431 -0
- package/dist/web/runtimeCapabilities.js +48 -0
- package/dist/web/sameAgentHandoffs.js +494 -0
- package/dist/web/server.js +15567 -0
- package/dist/web/sharedCollaborationCapsules.js +163 -0
- package/dist/web/soloSessionRelay.js +42 -0
- package/dist/web/soloWsHandler.js +138 -0
- package/dist/web/suggestedPlannerScheduler.js +56 -0
- package/dist/web/surfaceActivationPolicy.js +108 -0
- package/dist/web/surfaceCollaborators.js +61 -0
- package/dist/web/surfaceSystemStatus.js +263 -0
- package/dist/web/targetParticipants.js +77 -0
- package/dist/web/taskEvents.js +49 -0
- package/dist/web/taskLifecycleMessages.js +165 -0
- package/dist/web/taskLoops.js +732 -0
- package/dist/web/taskMemoryNotes.js +224 -0
- package/dist/web/taskNumbers.js +16 -0
- package/dist/web/taskOwnerGuards.js +49 -0
- package/dist/web/taskParticipantResolver.js +42 -0
- package/dist/web/taskParticipants.js +97 -0
- package/dist/web/taskSourceDetails.js +20 -0
- package/dist/web/taskStateViews.js +210 -0
- package/dist/web/taskStatusTransitions.js +9 -0
- package/dist/web/taskThreadFollowups.js +599 -0
- package/dist/web/taskThreadRuntimeClosure.js +685 -0
- package/dist/web/taskUpdateDelivery.js +104 -0
- package/dist/web/threadReplyContentHeuristics.js +30 -0
- package/dist/web/threadRoots.js +61 -0
- package/dist/web/threadTaskBindings.js +365 -0
- package/dist/web/uiPanelPromptGuidance.js +27 -0
- package/dist/web/workspaceMemoryHints.js +143 -0
- package/dist/web/workspaceToolPromptGuidance.js +30 -0
- package/dist/web/wsHandler.js +397 -0
- package/dist/web/wsSink.js +116 -0
- package/package.json +54 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { allocateNextChannelMessageSeq } from './channelMessageSequences.js';
|
|
3
|
+
export const PANEL_ACTION_SOURCE = 'panel_action';
|
|
4
|
+
export const PANEL_PLATFORM_EXEC_SOURCE = 'panel_platform_exec';
|
|
5
|
+
const PANEL_PLATFORM_EXEC_OUTPUT_PREVIEW_LIMIT = 800;
|
|
6
|
+
const PANEL_PLATFORM_EXEC_PAYLOAD_PREVIEW_LIMIT = 800;
|
|
7
|
+
function formatBoundedTextPreview(value, maxChars) {
|
|
8
|
+
if (!value)
|
|
9
|
+
return null;
|
|
10
|
+
if (value.length <= maxChars)
|
|
11
|
+
return value;
|
|
12
|
+
return `${value.slice(0, maxChars)}\n... [truncated ${value.length - maxChars} chars]`;
|
|
13
|
+
}
|
|
14
|
+
function formatBoundedJsonPreview(value, maxChars) {
|
|
15
|
+
const text = JSON.stringify(value, null, 2);
|
|
16
|
+
if (text.length <= maxChars)
|
|
17
|
+
return text;
|
|
18
|
+
return `${text.slice(0, maxChars)}\n... [truncated ${text.length - maxChars} chars]`;
|
|
19
|
+
}
|
|
20
|
+
export function insertPanelActionMessage(db, params) {
|
|
21
|
+
const createdAt = Date.now();
|
|
22
|
+
const messageId = randomUUID();
|
|
23
|
+
const seq = allocateNextChannelMessageSeq(db, params.channelId);
|
|
24
|
+
const selectedCount = params.payload.summary.selectedCount ?? params.payload.refs.selectedRowIndices?.length ?? 0;
|
|
25
|
+
const changedCount = params.payload.refs.changedRowIndices?.length ?? 0;
|
|
26
|
+
const selectionParts = [];
|
|
27
|
+
if (changedCount > 0) {
|
|
28
|
+
selectionParts.push(`${changedCount} row${changedCount === 1 ? '' : 's'} changed`);
|
|
29
|
+
}
|
|
30
|
+
if (selectedCount > 0) {
|
|
31
|
+
selectionParts.push(`${selectedCount} row${selectedCount === 1 ? '' : 's'} selected`);
|
|
32
|
+
}
|
|
33
|
+
const selectionText = selectionParts.length > 0 ? selectionParts.join(', ') : 'empty selection';
|
|
34
|
+
const staleText = params.payload.concurrency?.staleBase ? ', stale base version' : '';
|
|
35
|
+
const kindText = params.payload.submitKind === 'action' ? 'action' : params.payload.submitKind;
|
|
36
|
+
const content = `${params.payload.actor.username} submitted ${kindText} "${params.actionLabel}" on panel ${params.payload.panelId.slice(0, 8)} (${selectionText}${staleText})`;
|
|
37
|
+
db.prepare(`INSERT INTO channel_messages(
|
|
38
|
+
message_id, channel_id, sender_id, sender_name, sender_type, target, content,
|
|
39
|
+
seq, created_at, thread_root_id, message_kind, message_source, panel_ids
|
|
40
|
+
)
|
|
41
|
+
VALUES(?, ?, 'system', ?, 'system', ?, ?, ?, ?, ?, 'panel_action', ?, ?)`).run(messageId, params.channelId, params.payload.actor.username, params.target, content, seq, createdAt, params.threadRootId ?? null, PANEL_ACTION_SOURCE, JSON.stringify([params.payload.panelId]));
|
|
42
|
+
return {
|
|
43
|
+
messageId,
|
|
44
|
+
content,
|
|
45
|
+
seq,
|
|
46
|
+
createdAt,
|
|
47
|
+
senderName: params.payload.actor.username,
|
|
48
|
+
messageSource: PANEL_ACTION_SOURCE,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function insertPanelPlatformExecMessage(db, params) {
|
|
52
|
+
const payload = {
|
|
53
|
+
kind: 'panel_action_result',
|
|
54
|
+
mode: 'platform_exec',
|
|
55
|
+
panelId: params.panelId,
|
|
56
|
+
component: params.component,
|
|
57
|
+
actionId: params.actionId,
|
|
58
|
+
actionLabel: params.actionLabel,
|
|
59
|
+
requestedByUserId: params.actorUserId,
|
|
60
|
+
requestedByUsername: params.actorUsername,
|
|
61
|
+
status: params.status,
|
|
62
|
+
exitCode: params.exitCode,
|
|
63
|
+
signal: params.signal,
|
|
64
|
+
outputBytes: Buffer.byteLength(params.outputText ?? '', 'utf8'),
|
|
65
|
+
};
|
|
66
|
+
const outputPreview = formatBoundedTextPreview(params.outputText, PANEL_PLATFORM_EXEC_OUTPUT_PREVIEW_LIMIT);
|
|
67
|
+
const content = [
|
|
68
|
+
'Panel platform_exec result recorded.',
|
|
69
|
+
'',
|
|
70
|
+
`Panel: ${params.panelId}`,
|
|
71
|
+
`Component: ${params.component}`,
|
|
72
|
+
`Action: ${params.actionLabel} (${params.actionId})`,
|
|
73
|
+
`Result: status=${params.status} exit=${params.exitCode ?? 'n/a'} signal=${params.signal ?? 'n/a'}`,
|
|
74
|
+
`Triggered by: ${params.actorUsername} (${params.actorUserId})`,
|
|
75
|
+
'',
|
|
76
|
+
'Payload:',
|
|
77
|
+
'```json',
|
|
78
|
+
formatBoundedJsonPreview(payload, PANEL_PLATFORM_EXEC_PAYLOAD_PREVIEW_LIMIT),
|
|
79
|
+
'```',
|
|
80
|
+
...(outputPreview
|
|
81
|
+
? [
|
|
82
|
+
'',
|
|
83
|
+
'Output preview:',
|
|
84
|
+
'```text',
|
|
85
|
+
outputPreview,
|
|
86
|
+
'```',
|
|
87
|
+
]
|
|
88
|
+
: []),
|
|
89
|
+
'',
|
|
90
|
+
'This is a non-waking audit record. No agent run was started for this notice.',
|
|
91
|
+
].join('\n');
|
|
92
|
+
const createdAt = Date.now();
|
|
93
|
+
const messageId = randomUUID();
|
|
94
|
+
const seq = allocateNextChannelMessageSeq(db, params.channelId);
|
|
95
|
+
db.prepare(`INSERT INTO channel_messages(
|
|
96
|
+
message_id, channel_id, sender_id, sender_name, sender_type, target, content,
|
|
97
|
+
seq, created_at, thread_root_id, message_kind, message_source, panel_ids
|
|
98
|
+
)
|
|
99
|
+
VALUES(?, ?, 'system', 'Panel', 'system', ?, ?, ?, ?, ?, 'panel_platform_exec', ?, ?)`).run(messageId, params.channelId, params.target, content, seq, createdAt, params.threadRootId ?? null, PANEL_PLATFORM_EXEC_SOURCE, JSON.stringify([params.panelId]));
|
|
100
|
+
return {
|
|
101
|
+
messageId,
|
|
102
|
+
content,
|
|
103
|
+
seq,
|
|
104
|
+
createdAt,
|
|
105
|
+
senderName: 'Panel',
|
|
106
|
+
messageSource: PANEL_PLATFORM_EXEC_SOURCE,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createPromptContextSection, renderPromptContextSections, } from './promptContextSections.js';
|
|
2
|
+
export function buildPanelActionActivationPrompt(params) {
|
|
3
|
+
const sections = buildPanelActionActivationContextSections(params);
|
|
4
|
+
return renderPromptContextSections(sections);
|
|
5
|
+
}
|
|
6
|
+
export function buildPanelActionActivationContextSections(params) {
|
|
7
|
+
const sections = [];
|
|
8
|
+
const payload = params.payload;
|
|
9
|
+
const actionSection = createPromptContextSection('other', `[Panel submit]\nkind: ${payload.submitKind}\naction: ${payload.actionId ?? '(none)'}\npanel: ${payload.panelId}\nactor: @${payload.actor.username}`);
|
|
10
|
+
if (actionSection) {
|
|
11
|
+
sections.push(actionSection);
|
|
12
|
+
}
|
|
13
|
+
const payloadSection = createPromptContextSection('other', `[Panel submit payload]\n${JSON.stringify(payload, null, 2)}\n\nFull row details are not embedded. If refs.selectedRowIndices or refs.changedRowIndices is non-empty, run \`bigbang --format json panel read-rows --panel-id ${payload.panelId} --row-index <index>\` before producing the submit result so you can inspect selected or changed row fields and media refs. Repeat --row-index for multiple referenced rows.`);
|
|
14
|
+
if (payloadSection) {
|
|
15
|
+
sections.push(payloadSection);
|
|
16
|
+
}
|
|
17
|
+
return sections;
|
|
18
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
export function insertPanelAuditEvent(db, params) {
|
|
2
|
+
const result = db.prepare(`INSERT INTO panel_audit_events(
|
|
3
|
+
panel_id, event_type, version, changed, submit_kind,
|
|
4
|
+
action_id, failure_class, actor_type, actor_id, run_id,
|
|
5
|
+
conversation_id, scope_type, scope_id, metadata_json, created_at
|
|
6
|
+
)
|
|
7
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(params.panelId, params.eventType, params.version, params.changed ? JSON.stringify(params.changed) : null, params.submitKind ?? null, params.actionId ?? null, params.failureClass ?? null, params.actorType, params.actorId ?? null, params.runId ?? null, params.conversationId, params.scopeType, params.scopeId ?? null, JSON.stringify(params.metadata ?? {}), Date.now());
|
|
8
|
+
return Number(result.lastInsertRowid);
|
|
9
|
+
}
|
|
10
|
+
export function listPanelAuditEvents(db, panelId) {
|
|
11
|
+
const rows = db.prepare(`SELECT
|
|
12
|
+
id,
|
|
13
|
+
panel_id as panelId,
|
|
14
|
+
event_type as event,
|
|
15
|
+
created_at as timestamp,
|
|
16
|
+
version,
|
|
17
|
+
changed,
|
|
18
|
+
submit_kind as submitKind,
|
|
19
|
+
action_id as actionId,
|
|
20
|
+
failure_class as failureClass,
|
|
21
|
+
actor_type as actorType,
|
|
22
|
+
actor_id as actorId,
|
|
23
|
+
run_id as runId,
|
|
24
|
+
conversation_id as conversationId,
|
|
25
|
+
scope_type as scopeType,
|
|
26
|
+
scope_id as scopeId,
|
|
27
|
+
metadata_json as metadataJson
|
|
28
|
+
FROM panel_audit_events
|
|
29
|
+
WHERE panel_id = ?
|
|
30
|
+
ORDER BY id ASC`).all(panelId);
|
|
31
|
+
return rows.map(mapRawPanelAuditEventRow);
|
|
32
|
+
}
|
|
33
|
+
function mapRawPanelAuditEventRow(row) {
|
|
34
|
+
return {
|
|
35
|
+
id: row.id,
|
|
36
|
+
panelId: row.panelId,
|
|
37
|
+
event: row.event,
|
|
38
|
+
timestamp: row.timestamp,
|
|
39
|
+
version: row.version,
|
|
40
|
+
changed: row.changed,
|
|
41
|
+
submitKind: row.submitKind,
|
|
42
|
+
actionId: row.actionId,
|
|
43
|
+
failureClass: row.failureClass,
|
|
44
|
+
actorType: row.actorType,
|
|
45
|
+
actorId: row.actorId,
|
|
46
|
+
runId: row.runId,
|
|
47
|
+
conversationId: row.conversationId,
|
|
48
|
+
scopeType: row.scopeType,
|
|
49
|
+
scopeId: row.scopeId,
|
|
50
|
+
metadataJson: row.metadataJson,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function listPanelAuditEventsBounded(db, panelId, options) {
|
|
54
|
+
const clauses = ['panel_id = ?'];
|
|
55
|
+
const params = [panelId];
|
|
56
|
+
if (options.afterEventId !== undefined && options.afterEventId > 0) {
|
|
57
|
+
clauses.push('id > ?');
|
|
58
|
+
params.push(options.afterEventId);
|
|
59
|
+
}
|
|
60
|
+
if (options.since !== undefined && options.since > 0) {
|
|
61
|
+
clauses.push('created_at >= ?');
|
|
62
|
+
params.push(options.since);
|
|
63
|
+
}
|
|
64
|
+
if (options.eventTypes && options.eventTypes.length > 0) {
|
|
65
|
+
clauses.push(`event_type IN (${options.eventTypes.map(() => '?').join(', ')})`);
|
|
66
|
+
params.push(...options.eventTypes);
|
|
67
|
+
}
|
|
68
|
+
const limit = Math.max(options.limit, 1);
|
|
69
|
+
params.push(limit + 1);
|
|
70
|
+
const sql = `SELECT
|
|
71
|
+
id,
|
|
72
|
+
panel_id as panelId,
|
|
73
|
+
event_type as event,
|
|
74
|
+
created_at as timestamp,
|
|
75
|
+
version,
|
|
76
|
+
changed,
|
|
77
|
+
submit_kind as submitKind,
|
|
78
|
+
action_id as actionId,
|
|
79
|
+
failure_class as failureClass,
|
|
80
|
+
actor_type as actorType,
|
|
81
|
+
actor_id as actorId,
|
|
82
|
+
run_id as runId,
|
|
83
|
+
conversation_id as conversationId,
|
|
84
|
+
scope_type as scopeType,
|
|
85
|
+
scope_id as scopeId,
|
|
86
|
+
metadata_json as metadataJson
|
|
87
|
+
FROM panel_audit_events
|
|
88
|
+
WHERE ${clauses.join(' AND ')}
|
|
89
|
+
ORDER BY id ASC
|
|
90
|
+
LIMIT ?`;
|
|
91
|
+
const rows = db.prepare(sql).all(...params);
|
|
92
|
+
const hasMore = rows.length > limit;
|
|
93
|
+
return {
|
|
94
|
+
rows: rows.slice(0, limit).map(mapRawPanelAuditEventRow),
|
|
95
|
+
hasMore,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const PANEL_SEMANTIC_EVENT_LABELS = {
|
|
99
|
+
created: 'Panel created',
|
|
100
|
+
patched: 'Panel patched',
|
|
101
|
+
submitted: 'Panel submitted',
|
|
102
|
+
actioned: 'Panel action triggered',
|
|
103
|
+
failed: 'Panel event failed',
|
|
104
|
+
archived: 'Panel archived',
|
|
105
|
+
selection_committed: 'Selection committed',
|
|
106
|
+
annotation_saved: 'Annotation saved',
|
|
107
|
+
state_saved: 'State saved',
|
|
108
|
+
};
|
|
109
|
+
const METADATA_SUMMARY_KEYS = new Set([
|
|
110
|
+
'selectedCount',
|
|
111
|
+
'changedFieldCount',
|
|
112
|
+
'formValueKeyCount',
|
|
113
|
+
'stateKeys',
|
|
114
|
+
'sharedVersion',
|
|
115
|
+
'baseVersion',
|
|
116
|
+
'submittedVersion',
|
|
117
|
+
'exitCode',
|
|
118
|
+
'signal',
|
|
119
|
+
'outputBytes',
|
|
120
|
+
'toolRunStatus',
|
|
121
|
+
'toolActionMode',
|
|
122
|
+
'statusCode',
|
|
123
|
+
]);
|
|
124
|
+
function parseMetadata(metadataJson) {
|
|
125
|
+
try {
|
|
126
|
+
const parsed = JSON.parse(metadataJson);
|
|
127
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
128
|
+
? parsed
|
|
129
|
+
: {};
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return {};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function parseChanged(changed) {
|
|
136
|
+
if (!changed)
|
|
137
|
+
return undefined;
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(changed);
|
|
140
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === 'string') : undefined;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function stringArrayMetadata(metadata, key) {
|
|
147
|
+
const value = metadata[key];
|
|
148
|
+
if (!Array.isArray(value))
|
|
149
|
+
return undefined;
|
|
150
|
+
const result = value.filter((item) => typeof item === 'string' && item.trim().length > 0);
|
|
151
|
+
return result.length > 0 ? result : undefined;
|
|
152
|
+
}
|
|
153
|
+
function numberArrayMetadata(metadata, key) {
|
|
154
|
+
const value = metadata[key];
|
|
155
|
+
if (!Array.isArray(value))
|
|
156
|
+
return undefined;
|
|
157
|
+
const result = Array.from(new Set(value.filter((item) => Number.isInteger(item) && item >= 0)));
|
|
158
|
+
return result.length > 0 ? result : undefined;
|
|
159
|
+
}
|
|
160
|
+
function metadataSummary(metadata) {
|
|
161
|
+
const summary = {};
|
|
162
|
+
for (const key of METADATA_SUMMARY_KEYS) {
|
|
163
|
+
if (metadata[key] !== undefined)
|
|
164
|
+
summary[key] = metadata[key];
|
|
165
|
+
}
|
|
166
|
+
return summary;
|
|
167
|
+
}
|
|
168
|
+
function semanticWakePolicy(event, metadata) {
|
|
169
|
+
if (event.event === 'submitted')
|
|
170
|
+
return 'agent_visible';
|
|
171
|
+
if (event.event !== 'actioned')
|
|
172
|
+
return 'persisted';
|
|
173
|
+
if (metadata.rpc === true)
|
|
174
|
+
return 'persisted';
|
|
175
|
+
if (metadata.toolActionMode === 'platform_exec')
|
|
176
|
+
return 'persisted';
|
|
177
|
+
return 'agent_visible';
|
|
178
|
+
}
|
|
179
|
+
function summarizeSemanticEvent(event, metadata, refs) {
|
|
180
|
+
switch (event.event) {
|
|
181
|
+
case 'selection_committed': {
|
|
182
|
+
const selectedCount = typeof metadata.selectedCount === 'number'
|
|
183
|
+
? metadata.selectedCount
|
|
184
|
+
: refs.selectedRowIds?.length ?? refs.selectedRowIndices?.length ?? 0;
|
|
185
|
+
return `Selection committed: ${selectedCount} row${selectedCount === 1 ? '' : 's'}.`;
|
|
186
|
+
}
|
|
187
|
+
case 'annotation_saved': {
|
|
188
|
+
const count = typeof metadata.formValueKeyCount === 'number' ? metadata.formValueKeyCount : undefined;
|
|
189
|
+
return count != null ? `Annotation draft saved across ${count} form key${count === 1 ? '' : 's'}.` : 'Annotation draft saved.';
|
|
190
|
+
}
|
|
191
|
+
case 'state_saved': {
|
|
192
|
+
const keys = Array.isArray(metadata.stateKeys)
|
|
193
|
+
? metadata.stateKeys.filter((item) => typeof item === 'string')
|
|
194
|
+
: [];
|
|
195
|
+
return keys.length > 0 ? `Panel state saved: ${keys.join(', ')}.` : 'Panel state saved.';
|
|
196
|
+
}
|
|
197
|
+
case 'submitted': {
|
|
198
|
+
const pieces = [`Panel submitted${event.submitKind ? ` (${event.submitKind})` : ''}`];
|
|
199
|
+
const selected = typeof metadata.selectedCount === 'number' ? metadata.selectedCount : undefined;
|
|
200
|
+
const changed = typeof metadata.changedFieldCount === 'number' ? metadata.changedFieldCount : undefined;
|
|
201
|
+
if (selected != null)
|
|
202
|
+
pieces.push(`${selected} selected`);
|
|
203
|
+
if (changed != null)
|
|
204
|
+
pieces.push(`${changed} changed fields`);
|
|
205
|
+
return `${pieces.join(': ')}.`;
|
|
206
|
+
}
|
|
207
|
+
case 'actioned':
|
|
208
|
+
return event.actionId ? `Action triggered: ${event.actionId}.` : 'Panel action triggered.';
|
|
209
|
+
case 'patched': {
|
|
210
|
+
const changed = parseChanged(event.changed) ?? [];
|
|
211
|
+
return changed.length > 0 ? `Panel patched: ${changed.join(', ')}.` : 'Panel patched.';
|
|
212
|
+
}
|
|
213
|
+
case 'failed':
|
|
214
|
+
return event.failureClass ? `Panel event failed: ${event.failureClass}.` : 'Panel event failed.';
|
|
215
|
+
default:
|
|
216
|
+
return PANEL_SEMANTIC_EVENT_LABELS[event.event];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export function panelAuditEventToSemanticEvent(event) {
|
|
220
|
+
const metadata = parseMetadata(event.metadataJson);
|
|
221
|
+
const refs = {
|
|
222
|
+
...(numberArrayMetadata(metadata, 'selectedRowIndices') ? { selectedRowIndices: numberArrayMetadata(metadata, 'selectedRowIndices') } : {}),
|
|
223
|
+
...(stringArrayMetadata(metadata, 'selectedRowIds') ? { selectedRowIds: stringArrayMetadata(metadata, 'selectedRowIds') } : {}),
|
|
224
|
+
...(numberArrayMetadata(metadata, 'changedRowIndices') ? { changedRowIndices: numberArrayMetadata(metadata, 'changedRowIndices') } : {}),
|
|
225
|
+
...(stringArrayMetadata(metadata, 'changedRowIds') ? { changedRowIds: stringArrayMetadata(metadata, 'changedRowIds') } : {}),
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
eventId: event.id,
|
|
229
|
+
panelId: event.panelId,
|
|
230
|
+
event: event.event,
|
|
231
|
+
label: PANEL_SEMANTIC_EVENT_LABELS[event.event],
|
|
232
|
+
summary: summarizeSemanticEvent(event, metadata, refs),
|
|
233
|
+
timestamp: event.timestamp,
|
|
234
|
+
version: event.version,
|
|
235
|
+
wakePolicy: semanticWakePolicy(event, metadata),
|
|
236
|
+
...(parseChanged(event.changed) ? { changed: parseChanged(event.changed) } : {}),
|
|
237
|
+
...(event.submitKind ? { submitKind: event.submitKind } : {}),
|
|
238
|
+
...(event.actionId ? { actionId: event.actionId } : {}),
|
|
239
|
+
...(event.failureClass ? { failureClass: event.failureClass } : {}),
|
|
240
|
+
actor: {
|
|
241
|
+
type: event.actorType,
|
|
242
|
+
id: event.actorId,
|
|
243
|
+
runId: event.runId,
|
|
244
|
+
},
|
|
245
|
+
conversationId: event.conversationId,
|
|
246
|
+
scopeType: event.scopeType,
|
|
247
|
+
scopeId: event.scopeId,
|
|
248
|
+
refs,
|
|
249
|
+
metadataSummary: metadataSummary(metadata),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
export function listPanelSemanticEvents(db, panelId, options = {}) {
|
|
253
|
+
const limit = Math.min(Math.max(Number.isInteger(options.limit) ? options.limit ?? 50 : 50, 1), 100);
|
|
254
|
+
const eventTypes = options.eventTypes && options.eventTypes.length > 0
|
|
255
|
+
? Array.from(new Set(options.eventTypes))
|
|
256
|
+
: undefined;
|
|
257
|
+
const afterEventId = Number.isInteger(options.afterEventId) && (options.afterEventId ?? 0) > 0
|
|
258
|
+
? options.afterEventId
|
|
259
|
+
: undefined;
|
|
260
|
+
const since = Number.isFinite(options.since) && (options.since ?? 0) > 0
|
|
261
|
+
? options.since
|
|
262
|
+
: undefined;
|
|
263
|
+
const { rows, hasMore } = listPanelAuditEventsBounded(db, panelId, {
|
|
264
|
+
eventTypes,
|
|
265
|
+
afterEventId,
|
|
266
|
+
since,
|
|
267
|
+
limit,
|
|
268
|
+
});
|
|
269
|
+
return {
|
|
270
|
+
events: rows.map(panelAuditEventToSemanticEvent),
|
|
271
|
+
nextCursor: hasMore && rows.length > 0 ? String(rows[rows.length - 1].id) : null,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { log } from '@bbigbang/runtime-acp';
|
|
3
|
+
import { insertPanelAuditEvent } from './panelAudit.js';
|
|
4
|
+
import { getPanelById } from './panels.js';
|
|
5
|
+
export const PANEL_TASK_DONE_ARCHIVE_DELAY_MS = 7 * 24 * 60 * 60 * 1000;
|
|
6
|
+
export const PANEL_STALE_ARCHIVE_DELAY_MS = 30 * 24 * 60 * 60 * 1000;
|
|
7
|
+
export const PANEL_LIFECYCLE_POLL_INTERVAL_MS = 60_000;
|
|
8
|
+
function listPanelAttachmentArtifacts(db, panelId) {
|
|
9
|
+
return db.prepare(`SELECT id as attachmentId,
|
|
10
|
+
storage_path as storagePath
|
|
11
|
+
FROM attachments
|
|
12
|
+
WHERE scope_type = 'agent_upload'
|
|
13
|
+
AND scope_id = ?`).all(panelId);
|
|
14
|
+
}
|
|
15
|
+
function deleteAttachmentArtifactRows(db, artifacts) {
|
|
16
|
+
if (artifacts.length === 0)
|
|
17
|
+
return [];
|
|
18
|
+
const ids = artifacts.map((artifact) => artifact.attachmentId);
|
|
19
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
20
|
+
db.prepare(`DELETE FROM attachments WHERE id IN (${placeholders})`).run(...ids);
|
|
21
|
+
return Array.from(new Set(artifacts.map((artifact) => artifact.storagePath).filter((value) => !!value)));
|
|
22
|
+
}
|
|
23
|
+
export function unlinkPanelAttachmentStoragePaths(storagePaths) {
|
|
24
|
+
for (const storagePath of new Set(storagePaths)) {
|
|
25
|
+
try {
|
|
26
|
+
if (fs.existsSync(storagePath))
|
|
27
|
+
fs.unlinkSync(storagePath);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
log.warn('[panel-lifecycle] failed to delete cached panel attachment', {
|
|
31
|
+
storagePath,
|
|
32
|
+
error: String(error?.message ?? error),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function deletePanelAttachmentArtifactRows(db, params) {
|
|
38
|
+
if (params.attachmentIds && params.attachmentIds.length === 0)
|
|
39
|
+
return [];
|
|
40
|
+
const wantedIds = params.attachmentIds ? new Set(params.attachmentIds) : null;
|
|
41
|
+
const artifacts = listPanelAttachmentArtifacts(db, params.panelId)
|
|
42
|
+
.filter((artifact) => !wantedIds || wantedIds.has(artifact.attachmentId));
|
|
43
|
+
return deleteAttachmentArtifactRows(db, artifacts);
|
|
44
|
+
}
|
|
45
|
+
export function deletePanelAttachmentArtifacts(db, params) {
|
|
46
|
+
const storagePaths = deletePanelAttachmentArtifactRows(db, params);
|
|
47
|
+
unlinkPanelAttachmentStoragePaths(storagePaths);
|
|
48
|
+
}
|
|
49
|
+
function detachWorkspaceToolLinks(db, panelId) {
|
|
50
|
+
db.prepare(`UPDATE workspace_tools
|
|
51
|
+
SET source_panel_id = CASE WHEN source_panel_id = ? THEN NULL ELSE source_panel_id END,
|
|
52
|
+
panel_id = CASE WHEN panel_id = ? THEN NULL ELSE panel_id END,
|
|
53
|
+
updated_at = ?
|
|
54
|
+
WHERE source_panel_id = ?
|
|
55
|
+
OR panel_id = ?`).run(panelId, panelId, Date.now(), panelId, panelId);
|
|
56
|
+
}
|
|
57
|
+
export function hardDeletePanelWithArtifacts(db, panelId) {
|
|
58
|
+
const result = db.transaction(() => {
|
|
59
|
+
return hardDeletePanelRowsWithArtifacts(db, panelId);
|
|
60
|
+
})();
|
|
61
|
+
if (!result)
|
|
62
|
+
return null;
|
|
63
|
+
unlinkPanelAttachmentStoragePaths(result.storagePaths);
|
|
64
|
+
return result.panel;
|
|
65
|
+
}
|
|
66
|
+
export function hardDeletePanelRowsWithArtifacts(db, panelId) {
|
|
67
|
+
const panel = getPanelById(db, panelId);
|
|
68
|
+
if (!panel)
|
|
69
|
+
return null;
|
|
70
|
+
const artifacts = listPanelAttachmentArtifacts(db, panelId);
|
|
71
|
+
detachWorkspaceToolLinks(db, panelId);
|
|
72
|
+
const storagePaths = deleteAttachmentArtifactRows(db, artifacts);
|
|
73
|
+
db.prepare('DELETE FROM panels WHERE id = ?').run(panelId);
|
|
74
|
+
return { panel, storagePaths };
|
|
75
|
+
}
|
|
76
|
+
export function hardDeletePanelRowsForConversation(db, conversationId) {
|
|
77
|
+
const panelRows = db.prepare(`SELECT id
|
|
78
|
+
FROM panels
|
|
79
|
+
WHERE conversation_id = ?`).all(conversationId);
|
|
80
|
+
const panelIds = [];
|
|
81
|
+
const storagePaths = [];
|
|
82
|
+
for (const row of panelRows) {
|
|
83
|
+
const result = hardDeletePanelRowsWithArtifacts(db, row.id);
|
|
84
|
+
if (!result)
|
|
85
|
+
continue;
|
|
86
|
+
panelIds.push(row.id);
|
|
87
|
+
storagePaths.push(...result.storagePaths);
|
|
88
|
+
}
|
|
89
|
+
return { panelIds, storagePaths };
|
|
90
|
+
}
|
|
91
|
+
export function hardDeletePanelsForConversation(db, conversationId) {
|
|
92
|
+
const result = db.transaction(() => hardDeletePanelRowsForConversation(db, conversationId))();
|
|
93
|
+
unlinkPanelAttachmentStoragePaths(result.storagePaths);
|
|
94
|
+
return result.panelIds;
|
|
95
|
+
}
|
|
96
|
+
export function archivePanelWithArtifacts(db, params) {
|
|
97
|
+
const panel = getPanelById(db, params.panelId);
|
|
98
|
+
if (!panel)
|
|
99
|
+
return null;
|
|
100
|
+
if (panel.archivedAt)
|
|
101
|
+
return panel;
|
|
102
|
+
const now = params.now ?? Date.now();
|
|
103
|
+
const artifacts = listPanelAttachmentArtifacts(db, params.panelId);
|
|
104
|
+
const storagePaths = db.transaction(() => {
|
|
105
|
+
const paths = deleteAttachmentArtifactRows(db, artifacts);
|
|
106
|
+
db.prepare('DELETE FROM panel_rows WHERE panel_id = ?').run(params.panelId);
|
|
107
|
+
db.prepare('DELETE FROM panel_state WHERE panel_id = ?').run(params.panelId);
|
|
108
|
+
db.prepare('DELETE FROM panel_shared_state WHERE panel_id = ?').run(params.panelId);
|
|
109
|
+
db.prepare(`UPDATE panels
|
|
110
|
+
SET archived_at = ?,
|
|
111
|
+
updated_at = ?,
|
|
112
|
+
version = version + 1
|
|
113
|
+
WHERE id = ?
|
|
114
|
+
AND archived_at IS NULL`).run(now, now, params.panelId);
|
|
115
|
+
return paths;
|
|
116
|
+
})();
|
|
117
|
+
unlinkPanelAttachmentStoragePaths(storagePaths);
|
|
118
|
+
const archivedPanel = getPanelById(db, params.panelId);
|
|
119
|
+
if (!archivedPanel)
|
|
120
|
+
return null;
|
|
121
|
+
insertPanelAuditEvent(db, {
|
|
122
|
+
panelId: archivedPanel.id,
|
|
123
|
+
eventType: 'archived',
|
|
124
|
+
version: archivedPanel.version,
|
|
125
|
+
actorType: 'system',
|
|
126
|
+
actorId: null,
|
|
127
|
+
conversationId: archivedPanel.conversationId,
|
|
128
|
+
scopeType: archivedPanel.scopeType,
|
|
129
|
+
scopeId: archivedPanel.scopeId ?? null,
|
|
130
|
+
metadata: { reason: params.reason },
|
|
131
|
+
});
|
|
132
|
+
return archivedPanel;
|
|
133
|
+
}
|
|
134
|
+
export function processDuePanelArchives(db, params) {
|
|
135
|
+
const now = params?.now ?? Date.now();
|
|
136
|
+
const archivedPanelIds = new Set();
|
|
137
|
+
const deletedOrphanPanelIds = new Set();
|
|
138
|
+
const orphanRows = db.prepare(`SELECT p.id
|
|
139
|
+
FROM panels p
|
|
140
|
+
LEFT JOIN conversations c ON c.id = p.conversation_id
|
|
141
|
+
WHERE c.id IS NULL`).all();
|
|
142
|
+
const toolOrphanRows = db.prepare(`SELECT p.id
|
|
143
|
+
FROM panels p
|
|
144
|
+
WHERE p.scope_type = 'tool'`).all();
|
|
145
|
+
const toolOrphanIds = new Set(toolOrphanRows.map((row) => row.id));
|
|
146
|
+
for (const row of orphanRows) {
|
|
147
|
+
if (toolOrphanIds.has(row.id))
|
|
148
|
+
continue;
|
|
149
|
+
if (hardDeletePanelWithArtifacts(db, row.id)) {
|
|
150
|
+
deletedOrphanPanelIds.add(row.id);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const taskDoneCutoff = now - PANEL_TASK_DONE_ARCHIVE_DELAY_MS;
|
|
154
|
+
const taskDoneRows = db.prepare(`SELECT DISTINCT p.id
|
|
155
|
+
FROM panels p
|
|
156
|
+
JOIN conversations c ON c.id = p.conversation_id
|
|
157
|
+
JOIN thread_task_bindings ttb
|
|
158
|
+
ON ttb.channel_id = c.channel_id
|
|
159
|
+
AND ttb.thread_root_id = c.thread_root_id
|
|
160
|
+
JOIN tasks t ON t.task_id = ttb.task_id
|
|
161
|
+
WHERE p.archived_at IS NULL
|
|
162
|
+
AND t.status = 'done'
|
|
163
|
+
AND t.updated_at <= ?`).all(taskDoneCutoff);
|
|
164
|
+
for (const row of taskDoneRows) {
|
|
165
|
+
if (archivePanelWithArtifacts(db, { panelId: row.id, now, reason: 'task_done_retention' })) {
|
|
166
|
+
archivedPanelIds.add(row.id);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const staleCutoff = now - PANEL_STALE_ARCHIVE_DELAY_MS;
|
|
170
|
+
const staleRows = db.prepare(`SELECT id
|
|
171
|
+
FROM panels
|
|
172
|
+
WHERE archived_at IS NULL
|
|
173
|
+
AND scope_type != 'tool'
|
|
174
|
+
AND updated_at <= ?`).all(staleCutoff);
|
|
175
|
+
for (const row of staleRows) {
|
|
176
|
+
if (archivedPanelIds.has(row.id))
|
|
177
|
+
continue;
|
|
178
|
+
if (archivePanelWithArtifacts(db, { panelId: row.id, now, reason: 'stale_retention' })) {
|
|
179
|
+
archivedPanelIds.add(row.id);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
archivedPanelIds: Array.from(archivedPanelIds),
|
|
184
|
+
deletedOrphanPanelIds: Array.from(deletedOrphanPanelIds),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export function startPanelLifecycleService(params) {
|
|
188
|
+
const intervalMs = Math.max(5_000, params.intervalMs ?? PANEL_LIFECYCLE_POLL_INTERVAL_MS);
|
|
189
|
+
let closed = false;
|
|
190
|
+
let running = false;
|
|
191
|
+
let timer = null;
|
|
192
|
+
const tick = async () => {
|
|
193
|
+
if (closed || running)
|
|
194
|
+
return;
|
|
195
|
+
running = true;
|
|
196
|
+
try {
|
|
197
|
+
const result = processDuePanelArchives(params.db);
|
|
198
|
+
for (const panelId of result.archivedPanelIds) {
|
|
199
|
+
const panel = getPanelById(params.db, panelId);
|
|
200
|
+
if (panel)
|
|
201
|
+
params.onPanelArchived?.(panel);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
running = false;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
timer = setInterval(() => {
|
|
209
|
+
void tick();
|
|
210
|
+
}, intervalMs);
|
|
211
|
+
timer.unref?.();
|
|
212
|
+
return {
|
|
213
|
+
tick,
|
|
214
|
+
stop: () => {
|
|
215
|
+
closed = true;
|
|
216
|
+
if (!timer)
|
|
217
|
+
return;
|
|
218
|
+
clearInterval(timer);
|
|
219
|
+
timer = null;
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { PANEL_IMAGE_MEDIA_MAX_BYTES, PANEL_TEXT_MEDIA_MAX_BYTES, } from '@bbigbang/protocol';
|
|
2
|
+
const TEXT_LIKE_APPLICATION_MIME_TYPES = new Set([
|
|
3
|
+
'application/json',
|
|
4
|
+
'application/ld+json',
|
|
5
|
+
'application/xml',
|
|
6
|
+
'application/x-yaml',
|
|
7
|
+
'application/yaml',
|
|
8
|
+
'application/toml',
|
|
9
|
+
'application/javascript',
|
|
10
|
+
'application/typescript',
|
|
11
|
+
'application/x-tex',
|
|
12
|
+
'application/x-latex',
|
|
13
|
+
'application/latex',
|
|
14
|
+
]);
|
|
15
|
+
export function panelMediaDisplayTypeLimitBytes(displayType) {
|
|
16
|
+
return displayType === 'img' ? PANEL_IMAGE_MEDIA_MAX_BYTES : PANEL_TEXT_MEDIA_MAX_BYTES;
|
|
17
|
+
}
|
|
18
|
+
export function panelMediaDisplayTypeLimitLabel(displayType) {
|
|
19
|
+
return displayType === 'img' ? '5 MiB' : '1 MiB';
|
|
20
|
+
}
|
|
21
|
+
export function panelMediaSizeCapExceededError(displayType) {
|
|
22
|
+
return {
|
|
23
|
+
statusCode: 413,
|
|
24
|
+
error: `Media exceeds ${panelMediaDisplayTypeLimitLabel(displayType)} limit`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function panelMediaMimeMatchesDisplayType(mimeType, displayType) {
|
|
28
|
+
const normalizedMimeType = (mimeType ?? '').split(';', 1)[0]?.trim().toLowerCase() ?? '';
|
|
29
|
+
if (displayType === 'img')
|
|
30
|
+
return normalizedMimeType.startsWith('image/');
|
|
31
|
+
if (normalizedMimeType.startsWith('text/'))
|
|
32
|
+
return true;
|
|
33
|
+
if (normalizedMimeType.endsWith('+json') || normalizedMimeType.endsWith('+xml'))
|
|
34
|
+
return true;
|
|
35
|
+
return TEXT_LIKE_APPLICATION_MIME_TYPES.has(normalizedMimeType);
|
|
36
|
+
}
|
|
37
|
+
export function panelMediaDefaultFilename(displayType) {
|
|
38
|
+
if (displayType === 'img')
|
|
39
|
+
return 'image.bin';
|
|
40
|
+
if (displayType === 'latex')
|
|
41
|
+
return 'media.tex';
|
|
42
|
+
return 'media.txt';
|
|
43
|
+
}
|