@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,104 @@
|
|
|
1
|
+
function buildSummaryText(kind, taskNumber, status) {
|
|
2
|
+
switch (kind) {
|
|
3
|
+
case 'task_created':
|
|
4
|
+
return status === 'todo'
|
|
5
|
+
? `#t${taskNumber} created in todo.`
|
|
6
|
+
: `#t${taskNumber} created and moved to ${status}.`;
|
|
7
|
+
case 'task_claimed':
|
|
8
|
+
return status === 'in_progress'
|
|
9
|
+
? `#t${taskNumber} claimed and moved to in_progress.`
|
|
10
|
+
: `#t${taskNumber} claimed; status remains ${status}.`;
|
|
11
|
+
case 'task_unclaimed':
|
|
12
|
+
return status === 'todo'
|
|
13
|
+
? `#t${taskNumber} unclaimed and moved back to todo.`
|
|
14
|
+
: `#t${taskNumber} unclaimed; status remains ${status}.`;
|
|
15
|
+
case 'task_details_updated':
|
|
16
|
+
return `#t${taskNumber} details updated.`;
|
|
17
|
+
case 'task_status_changed':
|
|
18
|
+
default:
|
|
19
|
+
return `#t${taskNumber} moved to ${status}.`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function buildNoteTargets(kind, status) {
|
|
23
|
+
if (kind === 'task_status_changed' && status === 'done')
|
|
24
|
+
return ['notes/tasks.md', 'notes/archive/', 'notes/work-log/'];
|
|
25
|
+
return ['notes/tasks.md'];
|
|
26
|
+
}
|
|
27
|
+
function buildNoteGuidance(kind, status) {
|
|
28
|
+
if (kind === 'task_created') {
|
|
29
|
+
if (status === 'todo') {
|
|
30
|
+
return 'Update notes/tasks.md with the new task brief, scope, and the next owner or execution surface if that changed.';
|
|
31
|
+
}
|
|
32
|
+
return 'Update notes/tasks.md with the new task brief, current owner/work surface, and the immediate next step.';
|
|
33
|
+
}
|
|
34
|
+
if (kind === 'task_claimed') {
|
|
35
|
+
if (status === 'in_review') {
|
|
36
|
+
return 'Update notes/tasks.md with the claimed task ownership, finished result, and what is now waiting on review.';
|
|
37
|
+
}
|
|
38
|
+
return 'Update notes/tasks.md with the claimed task goal, current working state, and next step.';
|
|
39
|
+
}
|
|
40
|
+
if (kind === 'task_unclaimed') {
|
|
41
|
+
return 'Update notes/tasks.md with the released ownership, the new open state, and who should pick up the task next if that is now clearer.';
|
|
42
|
+
}
|
|
43
|
+
if (kind === 'task_details_updated') {
|
|
44
|
+
return 'Update notes/tasks.md with the clarified task title, brief, constraints, or done criteria so later recovery uses the latest task definition.';
|
|
45
|
+
}
|
|
46
|
+
switch (status) {
|
|
47
|
+
case 'todo':
|
|
48
|
+
return 'Update notes/tasks.md if the task scope, owner expectations, or next step changed after moving it back to todo.';
|
|
49
|
+
case 'in_progress':
|
|
50
|
+
return 'Update notes/tasks.md with the current goal, working state, blockers, and next step if this status change clarified them.';
|
|
51
|
+
case 'in_review':
|
|
52
|
+
return 'Update notes/tasks.md with the finished result, residual risks, and what is now waiting on review.';
|
|
53
|
+
case 'done':
|
|
54
|
+
return 'Archive this task: (1) write title + result + key decisions to notes/archive/tasks-<week>.md, (2) append one line to notes/archive/INDEX.md (<week>: <task title> — <one-line result>), (3) remove from notes/tasks.md, (4) record durable outcome in notes/work-log/<week>.md.';
|
|
55
|
+
default:
|
|
56
|
+
return 'Update notes/tasks.md if this task update changed your durable understanding of the work.';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function buildTaskUpdateDelivery(params) {
|
|
60
|
+
return {
|
|
61
|
+
kind: params.kind,
|
|
62
|
+
taskNumber: params.taskNumber,
|
|
63
|
+
status: params.status,
|
|
64
|
+
summaryText: buildSummaryText(params.kind, params.taskNumber, params.status),
|
|
65
|
+
recommendedNoteTargets: buildNoteTargets(params.kind, params.status),
|
|
66
|
+
noteGuidance: buildNoteGuidance(params.kind, params.status),
|
|
67
|
+
memoryReminder: 'If this task update affects your active task context or recovery state, update MEMORY.md too.',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function buildTaskStatusUpdateDelivery(params) {
|
|
71
|
+
return buildTaskUpdateDelivery({
|
|
72
|
+
kind: 'task_status_changed',
|
|
73
|
+
taskNumber: params.taskNumber,
|
|
74
|
+
status: params.status,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export function buildTaskCreatedDelivery(params) {
|
|
78
|
+
return buildTaskUpdateDelivery({
|
|
79
|
+
kind: 'task_created',
|
|
80
|
+
taskNumber: params.taskNumber,
|
|
81
|
+
status: params.status,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export function buildTaskClaimedDelivery(params) {
|
|
85
|
+
return buildTaskUpdateDelivery({
|
|
86
|
+
kind: 'task_claimed',
|
|
87
|
+
taskNumber: params.taskNumber,
|
|
88
|
+
status: params.status,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
export function buildTaskUnclaimedDelivery(params) {
|
|
92
|
+
return buildTaskUpdateDelivery({
|
|
93
|
+
kind: 'task_unclaimed',
|
|
94
|
+
taskNumber: params.taskNumber,
|
|
95
|
+
status: params.status,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
export function buildTaskDetailsUpdatedDelivery(params) {
|
|
99
|
+
return buildTaskUpdateDelivery({
|
|
100
|
+
kind: 'task_details_updated',
|
|
101
|
+
taskNumber: params.taskNumber,
|
|
102
|
+
status: params.status,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const LOW_INFORMATION_COORDINATION_PATTERNS = [
|
|
2
|
+
/^(?:@[\w-]+\s+)?(?:ok|okay|ack|roger|got it|on it|sounds good|will do|thanks|thank you)[.! ]*$/i,
|
|
3
|
+
/^(?:@[\w-]+\s+)?(?:收到|好的|继续|明白|了解|我来|我继续|我处理|我看下|我 review|我review|收到,我来|收到,我继续|先这样)[。!! ]*$/i,
|
|
4
|
+
/\b(?:will review|reviewing|i'?ll review|i'?ll continue|continuing|taking this|looking now)\b/i,
|
|
5
|
+
];
|
|
6
|
+
function isLikelySubstantiveMessage(text) {
|
|
7
|
+
if (text.length > 220)
|
|
8
|
+
return true;
|
|
9
|
+
if (text.includes('```'))
|
|
10
|
+
return true;
|
|
11
|
+
if (/[`$]/.test(text))
|
|
12
|
+
return true;
|
|
13
|
+
if (/(^|\s)(pnpm|npm|yarn|git|vitest|tsc|pytest|cargo|go test|bun)\b/i.test(text))
|
|
14
|
+
return true;
|
|
15
|
+
if (/(\/[\w.\-]+){2,}|[A-Za-z]:\\/.test(text))
|
|
16
|
+
return true;
|
|
17
|
+
if (/\b(build|test|diff|patch|commit|migration|schema|review-pass|blocking|finding|fix(ed)?|implemented?|validated?)\b/i.test(text))
|
|
18
|
+
return true;
|
|
19
|
+
if (text.split('\n').length >= 3)
|
|
20
|
+
return true;
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
export function isLikelyLowInformationThreadReply(text) {
|
|
24
|
+
const normalized = text.trim().replace(/\s+/g, ' ');
|
|
25
|
+
if (!normalized)
|
|
26
|
+
return false;
|
|
27
|
+
if (isLikelySubstantiveMessage(normalized))
|
|
28
|
+
return false;
|
|
29
|
+
return LOW_INFORMATION_COORDINATION_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
30
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { buildThreadShortId, normalizeMessageIdForThreadShortId, normalizeThreadShortIdInput, } from '@bbigbang/protocol';
|
|
2
|
+
function listTopLevelMessageIds(db, channelId) {
|
|
3
|
+
const rows = db.prepare(`SELECT message_id as messageId
|
|
4
|
+
FROM channel_messages
|
|
5
|
+
WHERE channel_id = ?
|
|
6
|
+
AND thread_root_id IS NULL
|
|
7
|
+
ORDER BY created_at ASC, seq ASC`).all(channelId);
|
|
8
|
+
return rows.map((row) => row.messageId);
|
|
9
|
+
}
|
|
10
|
+
function listTaskRootMessageIds(db, channelId) {
|
|
11
|
+
const rows = db.prepare(`SELECT message_id as messageId
|
|
12
|
+
FROM tasks
|
|
13
|
+
WHERE channel_id = ?
|
|
14
|
+
AND message_id IS NOT NULL
|
|
15
|
+
AND trim(message_id) != ''
|
|
16
|
+
ORDER BY created_at ASC, task_number ASC`).all(channelId);
|
|
17
|
+
return rows.map((row) => row.messageId);
|
|
18
|
+
}
|
|
19
|
+
export function findThreadRootMessageId(db, channelId, threadRootId) {
|
|
20
|
+
const normalizedThreadRootId = normalizeThreadShortIdInput(threadRootId);
|
|
21
|
+
if (!normalizedThreadRootId)
|
|
22
|
+
return null;
|
|
23
|
+
const normalizedLookupId = normalizeMessageIdForThreadShortId(normalizedThreadRootId);
|
|
24
|
+
const topLevelMessageIds = listTopLevelMessageIds(db, channelId);
|
|
25
|
+
const match = topLevelMessageIds.find((messageId) => buildThreadShortId(messageId) === normalizedThreadRootId);
|
|
26
|
+
if (match)
|
|
27
|
+
return match;
|
|
28
|
+
const legacyPrefixMatch = topLevelMessageIds.find((messageId) => {
|
|
29
|
+
const normalizedMessageId = normalizeMessageIdForThreadShortId(messageId);
|
|
30
|
+
return normalizedMessageId === normalizedLookupId || normalizedMessageId.startsWith(normalizedLookupId);
|
|
31
|
+
});
|
|
32
|
+
if (legacyPrefixMatch)
|
|
33
|
+
return legacyPrefixMatch;
|
|
34
|
+
const taskRootMessageIds = listTaskRootMessageIds(db, channelId);
|
|
35
|
+
const taskMatch = taskRootMessageIds.find((messageId) => buildThreadShortId(messageId) === normalizedThreadRootId);
|
|
36
|
+
if (taskMatch)
|
|
37
|
+
return taskMatch;
|
|
38
|
+
const taskLegacyPrefixMatch = taskRootMessageIds.find((messageId) => {
|
|
39
|
+
const normalizedMessageId = normalizeMessageIdForThreadShortId(messageId);
|
|
40
|
+
return normalizedMessageId === normalizedLookupId || normalizedMessageId.startsWith(normalizedLookupId);
|
|
41
|
+
});
|
|
42
|
+
if (taskLegacyPrefixMatch)
|
|
43
|
+
return taskLegacyPrefixMatch;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
export function resolveThreadRootLookup(db, channelId, threadRootId) {
|
|
47
|
+
const normalizedThreadRootId = normalizeThreadShortIdInput(threadRootId);
|
|
48
|
+
if (!normalizedThreadRootId)
|
|
49
|
+
return null;
|
|
50
|
+
const messageId = findThreadRootMessageId(db, channelId, normalizedThreadRootId);
|
|
51
|
+
if (!messageId) {
|
|
52
|
+
return {
|
|
53
|
+
canonicalThreadRootId: buildThreadShortId(normalizedThreadRootId),
|
|
54
|
+
messageId: null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
canonicalThreadRootId: buildThreadShortId(messageId),
|
|
59
|
+
messageId,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { buildThreadShortId } from '@bbigbang/protocol';
|
|
2
|
+
import { listRecentTargetParticipants, TARGET_PARTICIPANT_ACTIVE_WINDOW_MS, } from './targetParticipants.js';
|
|
3
|
+
import { listUnifiedRecentSurfaceParticipants } from './collaborationSurfaceState.js';
|
|
4
|
+
import { findThreadRootMessageId } from './threadRoots.js';
|
|
5
|
+
import { deleteNotificationV2StateForThreadRoots, syncTaskBindingSurfaceRoster, } from './notificationRounds.js';
|
|
6
|
+
function normalizeThreadRootId(threadRootId) {
|
|
7
|
+
return threadRootId ?? '';
|
|
8
|
+
}
|
|
9
|
+
function getTaskThreadRootIds(messageId) {
|
|
10
|
+
if (!messageId)
|
|
11
|
+
return [];
|
|
12
|
+
return [buildThreadShortId(messageId)];
|
|
13
|
+
}
|
|
14
|
+
export function getTaskThreadRootId(messageId) {
|
|
15
|
+
return messageId ? buildThreadShortId(messageId) : null;
|
|
16
|
+
}
|
|
17
|
+
function toBoundThreadTask(row) {
|
|
18
|
+
const threadRootId = getTaskThreadRootId(row.messageId);
|
|
19
|
+
if (!threadRootId)
|
|
20
|
+
return undefined;
|
|
21
|
+
return {
|
|
22
|
+
taskId: row.taskId,
|
|
23
|
+
channelId: row.channelId,
|
|
24
|
+
threadRootId,
|
|
25
|
+
linkedThreadId: threadRootId,
|
|
26
|
+
linkedThreadShortId: threadRootId,
|
|
27
|
+
taskNumber: row.taskNumber,
|
|
28
|
+
title: row.title,
|
|
29
|
+
description: row.description,
|
|
30
|
+
status: row.status,
|
|
31
|
+
assigneeId: row.assigneeId,
|
|
32
|
+
assigneeName: row.assigneeName,
|
|
33
|
+
assigneeDeleted: row.assigneeDeleted != null,
|
|
34
|
+
blockedReason: row.blockedReason,
|
|
35
|
+
blockedAt: row.blockedAt,
|
|
36
|
+
blockedBy: row.blockedBy,
|
|
37
|
+
boundAt: row.createdAt,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function listTaskParticipantsByTaskIds(db, taskIds) {
|
|
41
|
+
const uniqueTaskIds = [...new Set(taskIds.filter((taskId) => taskId.trim().length > 0))];
|
|
42
|
+
if (uniqueTaskIds.length === 0)
|
|
43
|
+
return new Map();
|
|
44
|
+
const placeholders = uniqueTaskIds.map(() => '?').join(', ');
|
|
45
|
+
const rows = db.prepare(`SELECT tp.task_id as taskId,
|
|
46
|
+
tp.agent_id as agentId,
|
|
47
|
+
a.name as name,
|
|
48
|
+
tp.participant_role as role,
|
|
49
|
+
CASE WHEN acm.agent_id IS NULL THEN 0 ELSE 1 END as isChannelMember
|
|
50
|
+
FROM task_participants tp
|
|
51
|
+
JOIN agents a ON a.agent_id = tp.agent_id
|
|
52
|
+
JOIN tasks t ON t.task_id = tp.task_id
|
|
53
|
+
LEFT JOIN agent_channel_memberships acm ON acm.agent_id = tp.agent_id AND acm.channel_id = t.channel_id
|
|
54
|
+
WHERE tp.task_id IN (${placeholders})
|
|
55
|
+
ORDER BY CASE WHEN tp.participant_role = 'owner' THEN 0 ELSE 1 END, tp.last_updated_at ASC`).all(...uniqueTaskIds);
|
|
56
|
+
const participantsByTask = new Map();
|
|
57
|
+
for (const row of rows) {
|
|
58
|
+
const participants = participantsByTask.get(row.taskId) ?? [];
|
|
59
|
+
participants.push({
|
|
60
|
+
agentId: row.agentId,
|
|
61
|
+
name: row.name,
|
|
62
|
+
role: row.role === 'owner' ? 'owner' : 'collaborator',
|
|
63
|
+
isChannelMember: row.isChannelMember === 1,
|
|
64
|
+
});
|
|
65
|
+
participantsByTask.set(row.taskId, participants);
|
|
66
|
+
}
|
|
67
|
+
return participantsByTask;
|
|
68
|
+
}
|
|
69
|
+
function toTaskInfo(row, participants = []) {
|
|
70
|
+
const linkedThreadId = getTaskThreadRootId(row.messageId);
|
|
71
|
+
return {
|
|
72
|
+
taskId: row.taskId,
|
|
73
|
+
channelId: row.channelId,
|
|
74
|
+
taskNumber: row.taskNumber,
|
|
75
|
+
title: row.title,
|
|
76
|
+
...(row.description != null ? { description: row.description } : {}),
|
|
77
|
+
executionMode: row.executionMode,
|
|
78
|
+
activeLoopId: row.activeLoopId,
|
|
79
|
+
activeLoopStatus: row.activeLoopStatus ?? null,
|
|
80
|
+
status: row.status,
|
|
81
|
+
assigneeId: row.assigneeId,
|
|
82
|
+
assigneeName: row.assigneeName,
|
|
83
|
+
assigneeDeleted: row.assigneeDeleted != null,
|
|
84
|
+
messageId: row.messageId,
|
|
85
|
+
participants,
|
|
86
|
+
linkedThreadId,
|
|
87
|
+
linkedThreadShortId: linkedThreadId,
|
|
88
|
+
blockedReason: row.blockedReason,
|
|
89
|
+
blockedAt: row.blockedAt,
|
|
90
|
+
blockedBy: row.blockedBy,
|
|
91
|
+
createdAt: row.createdAt,
|
|
92
|
+
updatedAt: row.updatedAt,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function getBoundTaskForThread(db, params) {
|
|
96
|
+
const threadRootId = normalizeThreadRootId(params.threadRootId);
|
|
97
|
+
if (!threadRootId)
|
|
98
|
+
return undefined;
|
|
99
|
+
const rootMessageId = findThreadRootMessageId(db, params.channelId, threadRootId);
|
|
100
|
+
if (!rootMessageId)
|
|
101
|
+
return undefined;
|
|
102
|
+
const row = db.prepare(`SELECT t.task_id as taskId,
|
|
103
|
+
t.channel_id as channelId,
|
|
104
|
+
t.task_number as taskNumber,
|
|
105
|
+
t.title as title,
|
|
106
|
+
t.description as description,
|
|
107
|
+
t.execution_mode as executionMode,
|
|
108
|
+
t.active_loop_id as activeLoopId,
|
|
109
|
+
l.status as activeLoopStatus,
|
|
110
|
+
t.status as status,
|
|
111
|
+
t.claimed_by_agent_id as assigneeId,
|
|
112
|
+
t.claimed_by_name as assigneeName,
|
|
113
|
+
assignee_agent.deleted_at as assigneeDeleted,
|
|
114
|
+
t.message_id as messageId,
|
|
115
|
+
t.blocked_reason as blockedReason,
|
|
116
|
+
t.blocked_at as blockedAt,
|
|
117
|
+
t.blocked_by as blockedBy,
|
|
118
|
+
t.created_at as createdAt,
|
|
119
|
+
t.updated_at as updatedAt
|
|
120
|
+
FROM tasks t
|
|
121
|
+
LEFT JOIN task_loops l ON l.loop_id = t.active_loop_id
|
|
122
|
+
LEFT JOIN agents assignee_agent ON assignee_agent.agent_id = t.claimed_by_agent_id
|
|
123
|
+
WHERE t.channel_id = ? AND t.message_id = ?
|
|
124
|
+
LIMIT 1`).get(params.channelId, rootMessageId);
|
|
125
|
+
return row ? toBoundThreadTask(row) : undefined;
|
|
126
|
+
}
|
|
127
|
+
export function getThreadBindingForTask(db, taskId) {
|
|
128
|
+
const row = db.prepare(`SELECT channel_id as channelId, message_id as messageId
|
|
129
|
+
FROM tasks
|
|
130
|
+
WHERE task_id = ? AND message_id IS NOT NULL
|
|
131
|
+
LIMIT 1`).get(taskId);
|
|
132
|
+
const threadRootId = getTaskThreadRootId(row?.messageId);
|
|
133
|
+
const threadRootIds = getTaskThreadRootIds(row?.messageId);
|
|
134
|
+
if (!row || !threadRootId)
|
|
135
|
+
return undefined;
|
|
136
|
+
return { channelId: row.channelId, threadRootId, threadRootIds };
|
|
137
|
+
}
|
|
138
|
+
function listBoundTaskThreadParticipants(db, params) {
|
|
139
|
+
return listCurrentTaskPeerUpdateParticipants(db, {
|
|
140
|
+
taskId: params.taskId,
|
|
141
|
+
ownerAgentId: params.ownerAgentId,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function mergeThreadParticipants(...lists) {
|
|
145
|
+
const merged = new Map();
|
|
146
|
+
for (const list of lists) {
|
|
147
|
+
for (const participant of list) {
|
|
148
|
+
const existing = merged.get(participant.agentId);
|
|
149
|
+
if (!existing) {
|
|
150
|
+
merged.set(participant.agentId, participant);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
merged.set(participant.agentId, {
|
|
154
|
+
...existing,
|
|
155
|
+
name: participant.name || existing.name,
|
|
156
|
+
role: existing.role === 'owner' || participant.role === 'owner' ? 'owner' : 'participant',
|
|
157
|
+
joinedAt: Math.min(existing.joinedAt, participant.joinedAt),
|
|
158
|
+
lastActiveAt: Math.max(existing.lastActiveAt, participant.lastActiveAt),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return [...merged.values()].sort((left, right) => {
|
|
163
|
+
const leftOrder = left.role === 'owner' ? 0 : 1;
|
|
164
|
+
const rightOrder = right.role === 'owner' ? 0 : 1;
|
|
165
|
+
if (leftOrder !== rightOrder)
|
|
166
|
+
return leftOrder - rightOrder;
|
|
167
|
+
if (left.lastActiveAt !== right.lastActiveAt)
|
|
168
|
+
return right.lastActiveAt - left.lastActiveAt;
|
|
169
|
+
return left.name.localeCompare(right.name);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
export function listCurrentTaskPeerUpdateParticipants(db, params) {
|
|
173
|
+
const rows = db.prepare(`SELECT a.agent_id as agentId,
|
|
174
|
+
a.name,
|
|
175
|
+
CASE
|
|
176
|
+
WHEN a.agent_id = ? OR tp.participant_role = 'owner' THEN 'owner'
|
|
177
|
+
ELSE 'participant'
|
|
178
|
+
END as role,
|
|
179
|
+
tp.joined_at as joinedAt,
|
|
180
|
+
tp.last_updated_at as lastActiveAt
|
|
181
|
+
FROM task_participants tp
|
|
182
|
+
JOIN agents a ON a.agent_id = tp.agent_id
|
|
183
|
+
WHERE tp.task_id = ?
|
|
184
|
+
UNION ALL
|
|
185
|
+
SELECT a.agent_id as agentId,
|
|
186
|
+
a.name,
|
|
187
|
+
'owner' as role,
|
|
188
|
+
t.created_at as joinedAt,
|
|
189
|
+
t.updated_at as lastActiveAt
|
|
190
|
+
FROM tasks t
|
|
191
|
+
JOIN agents a ON a.agent_id = t.claimed_by_agent_id
|
|
192
|
+
WHERE t.task_id = ?
|
|
193
|
+
AND t.claimed_by_agent_id IS NOT NULL`).all(params.ownerAgentId ?? null, params.taskId, params.taskId);
|
|
194
|
+
return mergeThreadParticipants(rows);
|
|
195
|
+
}
|
|
196
|
+
export function syncTaskThreadOwner(db, params) {
|
|
197
|
+
const binding = getThreadBindingForTask(db, params.taskId);
|
|
198
|
+
if (!binding)
|
|
199
|
+
return;
|
|
200
|
+
const collaboratorAgentIds = db.prepare(`SELECT agent_id as agentId, participant_role as participantRole
|
|
201
|
+
FROM task_participants
|
|
202
|
+
WHERE task_id = ?`).all(params.taskId)
|
|
203
|
+
.filter((row) => row.participantRole !== 'owner')
|
|
204
|
+
.map((row) => row.agentId);
|
|
205
|
+
for (const threadRootId of binding.threadRootIds) {
|
|
206
|
+
syncTaskBindingSurfaceRoster(db, {
|
|
207
|
+
channelId: binding.channelId,
|
|
208
|
+
threadRootId,
|
|
209
|
+
taskId: params.taskId,
|
|
210
|
+
ownerAgentId: params.agentId ?? null,
|
|
211
|
+
collaboratorAgentIds,
|
|
212
|
+
now: params.lastActiveAt,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
export function demoteTaskThreadParticipantsForDone(db, params) {
|
|
217
|
+
const binding = getThreadBindingForTask(db, params.taskId);
|
|
218
|
+
if (!binding)
|
|
219
|
+
return;
|
|
220
|
+
for (const threadRootId of binding.threadRootIds) {
|
|
221
|
+
db.prepare(`DELETE FROM agent_surface_roster
|
|
222
|
+
WHERE channel_id = ?
|
|
223
|
+
AND surface_thread_key = ?
|
|
224
|
+
AND source IN ('surface_owner', 'task_binding_current')`).run(binding.channelId, threadRootId);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export function clearTaskThreadState(db, params) {
|
|
228
|
+
const binding = getThreadBindingForTask(db, params.taskId);
|
|
229
|
+
if (!binding)
|
|
230
|
+
return;
|
|
231
|
+
for (const threadRootId of binding.threadRootIds) {
|
|
232
|
+
db.prepare(`DELETE FROM target_participants
|
|
233
|
+
WHERE channel_id = ? AND thread_root_id = ?`).run(binding.channelId, threadRootId);
|
|
234
|
+
db.prepare(`DELETE FROM agent_message_checkpoints
|
|
235
|
+
WHERE channel_id = ? AND thread_root_id = ?`).run(binding.channelId, threadRootId);
|
|
236
|
+
}
|
|
237
|
+
deleteNotificationV2StateForThreadRoots(db, {
|
|
238
|
+
channelId: binding.channelId,
|
|
239
|
+
threadRootIds: binding.threadRootIds,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
export function listChannelTasks(db, params) {
|
|
243
|
+
const rows = (params.status && params.status !== 'all'
|
|
244
|
+
? db.prepare(`SELECT t.task_id as taskId,
|
|
245
|
+
t.channel_id as channelId,
|
|
246
|
+
t.task_number as taskNumber,
|
|
247
|
+
t.title as title,
|
|
248
|
+
t.description as description,
|
|
249
|
+
t.execution_mode as executionMode,
|
|
250
|
+
t.active_loop_id as activeLoopId,
|
|
251
|
+
l.status as activeLoopStatus,
|
|
252
|
+
t.status as status,
|
|
253
|
+
t.claimed_by_agent_id as assigneeId,
|
|
254
|
+
t.claimed_by_name as assigneeName,
|
|
255
|
+
assignee_agent.deleted_at as assigneeDeleted,
|
|
256
|
+
t.message_id as messageId,
|
|
257
|
+
t.blocked_reason as blockedReason,
|
|
258
|
+
t.blocked_at as blockedAt,
|
|
259
|
+
t.blocked_by as blockedBy,
|
|
260
|
+
t.created_at as createdAt,
|
|
261
|
+
t.updated_at as updatedAt
|
|
262
|
+
FROM tasks t
|
|
263
|
+
LEFT JOIN task_loops l ON l.loop_id = t.active_loop_id
|
|
264
|
+
LEFT JOIN agents assignee_agent ON assignee_agent.agent_id = t.claimed_by_agent_id
|
|
265
|
+
WHERE t.channel_id = ? AND t.status = ?
|
|
266
|
+
ORDER BY t.task_number ASC`).all(params.channelId, params.status)
|
|
267
|
+
: db.prepare(`SELECT t.task_id as taskId,
|
|
268
|
+
t.channel_id as channelId,
|
|
269
|
+
t.task_number as taskNumber,
|
|
270
|
+
t.title as title,
|
|
271
|
+
t.description as description,
|
|
272
|
+
t.execution_mode as executionMode,
|
|
273
|
+
t.active_loop_id as activeLoopId,
|
|
274
|
+
l.status as activeLoopStatus,
|
|
275
|
+
t.status as status,
|
|
276
|
+
t.claimed_by_agent_id as assigneeId,
|
|
277
|
+
t.claimed_by_name as assigneeName,
|
|
278
|
+
assignee_agent.deleted_at as assigneeDeleted,
|
|
279
|
+
t.message_id as messageId,
|
|
280
|
+
t.blocked_reason as blockedReason,
|
|
281
|
+
t.blocked_at as blockedAt,
|
|
282
|
+
t.blocked_by as blockedBy,
|
|
283
|
+
t.created_at as createdAt,
|
|
284
|
+
t.updated_at as updatedAt
|
|
285
|
+
FROM tasks t
|
|
286
|
+
LEFT JOIN task_loops l ON l.loop_id = t.active_loop_id
|
|
287
|
+
LEFT JOIN agents assignee_agent ON assignee_agent.agent_id = t.claimed_by_agent_id
|
|
288
|
+
WHERE t.channel_id = ?
|
|
289
|
+
ORDER BY t.task_number ASC`).all(params.channelId));
|
|
290
|
+
const participantsByTask = listTaskParticipantsByTaskIds(db, rows.map((row) => row.taskId));
|
|
291
|
+
return rows.map((row) => toTaskInfo(row, participantsByTask.get(row.taskId) ?? []));
|
|
292
|
+
}
|
|
293
|
+
export function getChannelTaskByNumber(db, params) {
|
|
294
|
+
const row = db.prepare(`SELECT t.task_id as taskId,
|
|
295
|
+
t.channel_id as channelId,
|
|
296
|
+
t.task_number as taskNumber,
|
|
297
|
+
t.title as title,
|
|
298
|
+
t.description as description,
|
|
299
|
+
t.execution_mode as executionMode,
|
|
300
|
+
t.active_loop_id as activeLoopId,
|
|
301
|
+
l.status as activeLoopStatus,
|
|
302
|
+
t.status as status,
|
|
303
|
+
t.claimed_by_agent_id as assigneeId,
|
|
304
|
+
t.claimed_by_name as assigneeName,
|
|
305
|
+
assignee_agent.deleted_at as assigneeDeleted,
|
|
306
|
+
t.message_id as messageId,
|
|
307
|
+
t.blocked_reason as blockedReason,
|
|
308
|
+
t.blocked_at as blockedAt,
|
|
309
|
+
t.blocked_by as blockedBy,
|
|
310
|
+
t.created_at as createdAt,
|
|
311
|
+
t.updated_at as updatedAt
|
|
312
|
+
FROM tasks t
|
|
313
|
+
LEFT JOIN task_loops l ON l.loop_id = t.active_loop_id
|
|
314
|
+
LEFT JOIN agents assignee_agent ON assignee_agent.agent_id = t.claimed_by_agent_id
|
|
315
|
+
WHERE t.channel_id = ? AND t.task_number = ?
|
|
316
|
+
LIMIT 1`).get(params.channelId, params.taskNumber);
|
|
317
|
+
if (!row)
|
|
318
|
+
return undefined;
|
|
319
|
+
const participantsByTask = listTaskParticipantsByTaskIds(db, [row.taskId]);
|
|
320
|
+
return toTaskInfo(row, participantsByTask.get(row.taskId) ?? []);
|
|
321
|
+
}
|
|
322
|
+
export function listThreadCollaborationParticipants(db, params) {
|
|
323
|
+
const boundTask = getBoundTaskForThread(db, params);
|
|
324
|
+
const recentParticipants = listUnifiedRecentSurfaceParticipants(db, {
|
|
325
|
+
channelId: params.channelId,
|
|
326
|
+
threadRootId: params.threadRootId,
|
|
327
|
+
});
|
|
328
|
+
const boundTaskParticipants = boundTask?.threadRootId
|
|
329
|
+
? listBoundTaskThreadParticipants(db, {
|
|
330
|
+
taskId: boundTask.taskId,
|
|
331
|
+
ownerAgentId: boundTask.assigneeId,
|
|
332
|
+
})
|
|
333
|
+
: [];
|
|
334
|
+
if (!boundTask)
|
|
335
|
+
return recentParticipants;
|
|
336
|
+
if (boundTask.status === 'done') {
|
|
337
|
+
return listRecentTargetParticipants(db, {
|
|
338
|
+
channelId: params.channelId,
|
|
339
|
+
threadRootId: params.threadRootId,
|
|
340
|
+
activeSince: Date.now() - TARGET_PARTICIPANT_ACTIVE_WINDOW_MS,
|
|
341
|
+
})
|
|
342
|
+
.map((participant) => ({ ...participant, role: 'participant' }))
|
|
343
|
+
.sort((left, right) => {
|
|
344
|
+
if (left.lastActiveAt !== right.lastActiveAt)
|
|
345
|
+
return right.lastActiveAt - left.lastActiveAt;
|
|
346
|
+
return left.name.localeCompare(right.name);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return boundTaskParticipants;
|
|
350
|
+
}
|
|
351
|
+
export function getThreadCollaborationSummary(db, params) {
|
|
352
|
+
const boundTask = getBoundTaskForThread(db, params);
|
|
353
|
+
const participants = listThreadCollaborationParticipants(db, params);
|
|
354
|
+
const ownerParticipant = participants.find((participant) => participant.role === 'owner');
|
|
355
|
+
const suppressOwner = boundTask?.status === 'done';
|
|
356
|
+
const taskOwner = boundTask && boundTask.status !== 'done' && boundTask.assigneeName
|
|
357
|
+
? { ownerAgentId: boundTask.assigneeId ?? null, ownerName: boundTask.assigneeName }
|
|
358
|
+
: null;
|
|
359
|
+
return {
|
|
360
|
+
...(boundTask ? { boundTask } : {}),
|
|
361
|
+
ownerAgentId: suppressOwner ? null : taskOwner?.ownerAgentId ?? ownerParticipant?.agentId ?? null,
|
|
362
|
+
ownerName: suppressOwner ? null : taskOwner?.ownerName ?? ownerParticipant?.name ?? null,
|
|
363
|
+
participants: participants.map((participant) => participant.name),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function buildUiPanelGuidancePromptSection(options) {
|
|
2
|
+
if (options?.layoutPlan) {
|
|
3
|
+
return [
|
|
4
|
+
'[UI panel guidance]',
|
|
5
|
+
'UI panel layout planning mode is enabled for this chat conversation.',
|
|
6
|
+
'Before designing the layout, run `bigbang panel components` for the current component props, dataset, state, and action contract.',
|
|
7
|
+
'Do not run `bigbang panel create`, `bigbang panel upsert`, or `bigbang panel patch` in this planning run. Send a compact chat wireframe with `bigbang message send` and wait for the user to confirm in a later turn.',
|
|
8
|
+
'The wireframe must cover: component choice, data source plan, fields/filter/sort labels, row-card Columns layout, action button placement, and an explicit confirmation prompt.',
|
|
9
|
+
'For status, monitoring, dashboard, or tool-control views, prefer RowTemplateGrid Columns instead of stacking every field vertically in one Card.',
|
|
10
|
+
'Do not guess props, template nodes, dataset fields, media slot shapes, or action keys from memory or repo search.',
|
|
11
|
+
].join('\n');
|
|
12
|
+
}
|
|
13
|
+
return [
|
|
14
|
+
'[UI panel guidance]',
|
|
15
|
+
'UI panel mode is enabled for this chat conversation.',
|
|
16
|
+
'This slash command requires a delivered curated UI panel. Do not satisfy it with only a static image, CSV path, artifact path, or ordinary text reply.',
|
|
17
|
+
'Before the first panel create/update in a run, run `bigbang panel components` for the current component props, dataset, state, and action contract.',
|
|
18
|
+
'For detailed RowTemplateGrid template grammar, use the mounted ui-panel skill docs. Do not guess props, template nodes, dataset fields, or media slot shapes from memory or repo search.',
|
|
19
|
+
'For status, monitoring, dashboard, or tool-control views, prefer RowTemplateGrid Columns instead of stacking every field vertically in one Card.',
|
|
20
|
+
'For a new panel, use `bigbang panel create`. For continuing the same annotation/evaluation/workbench across later turns, use `bigbang panel upsert` with a semantic handle such as pigai-eval-results or image-review-batch.',
|
|
21
|
+
'Prefer one stable delivery panel per /panel run. Use scratch panels only when unavoidable, and do not treat un-attached experimental panels as delivered work.',
|
|
22
|
+
'For inline rows, `bigbang panel create` expects JSON like: {"component":"RowTemplateGrid","props":{"title":"Title","fields":[{"name":"name","type":"string","label":"Name"}],"template":{"type":"TextField","field":"name"}},"dataset":{"source":{"kind":"inline_rows","rows":[{"rowId":"row-1","fields":{"name":"alpha"}}]}},"actions":[]}. Add "handle":"stable-handle" and use `bigbang panel upsert` only when you need stable-handle updates.',
|
|
23
|
+
'After a successful `bigbang panel create` or `bigbang panel upsert`, spot-check with `bigbang panel read-rows --panel-id <returned_panel_id> --limit 3`, remember the handle or panel_id plus dataset path, and prefer `bigbang panel patch --panel-id <id>` only for precise same-component updates when you already know the target panel_id.',
|
|
24
|
+
'Send the final reply with `bigbang message send --kind final --panel-id <returned_panel_id>`. The platform rejects a /panel final that does not attach the produced panel; if the panel cannot be created, send final with `--slash-command-failed "<reason>"`.',
|
|
25
|
+
'If `bigbang panel patch` or `bigbang panel upsert` reports version_conflict, read the latest panel state/events/rows with Bigbang, merge user changes, then retry with currentVersion; do not blindly overwrite saved user state or annotations.',
|
|
26
|
+
].join('\n');
|
|
27
|
+
}
|