negotium 0.1.7 → 0.1.9
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/README.md +26 -2
- package/dist/canonical-mcp-bridge.js +14 -0
- package/dist/canonical-mcp-bridge.js.map +9 -0
- package/dist/chunk-10ct8814.js +429 -0
- package/dist/chunk-10ct8814.js.map +11 -0
- package/dist/chunk-18fzftjf.js +214 -0
- package/dist/chunk-18fzftjf.js.map +10 -0
- package/dist/chunk-1k7t7bnx.js +97 -0
- package/dist/chunk-1k7t7bnx.js.map +10 -0
- package/dist/chunk-1msje2zf.js +23 -0
- package/dist/chunk-1msje2zf.js.map +10 -0
- package/dist/chunk-2dgkah1z.js +706 -0
- package/dist/chunk-2dgkah1z.js.map +14 -0
- package/dist/chunk-3t4dtm26.js +61 -0
- package/dist/chunk-3t4dtm26.js.map +10 -0
- package/dist/chunk-3vn2vnj1.js +51 -0
- package/dist/chunk-3vn2vnj1.js.map +10 -0
- package/dist/chunk-6hknphtc.js +2054 -0
- package/dist/chunk-6hknphtc.js.map +29 -0
- package/dist/chunk-6tfzpcvk.js +2149 -0
- package/dist/chunk-6tfzpcvk.js.map +15 -0
- package/dist/chunk-8g0c4as0.js +23 -0
- package/dist/chunk-8g0c4as0.js.map +10 -0
- package/dist/chunk-9spx3h9n.js +279 -0
- package/dist/chunk-9spx3h9n.js.map +10 -0
- package/dist/chunk-c500xe9z.js +33 -0
- package/dist/chunk-c500xe9z.js.map +10 -0
- package/dist/chunk-e9b2xpff.js +147 -0
- package/dist/chunk-e9b2xpff.js.map +10 -0
- package/dist/chunk-edb07sjp.js +567 -0
- package/dist/chunk-edb07sjp.js.map +14 -0
- package/dist/chunk-eexqsee7.js +368 -0
- package/dist/chunk-eexqsee7.js.map +11 -0
- package/dist/chunk-efyxk97v.js +500 -0
- package/dist/chunk-efyxk97v.js.map +13 -0
- package/dist/chunk-epqxwkvh.js +3534 -0
- package/dist/chunk-epqxwkvh.js.map +20 -0
- package/dist/chunk-f1pzh3xh.js +115 -0
- package/dist/chunk-f1pzh3xh.js.map +10 -0
- package/dist/chunk-f5xgprg1.js +59 -0
- package/dist/chunk-f5xgprg1.js.map +10 -0
- package/dist/chunk-f97qybwk.js +584 -0
- package/dist/chunk-f97qybwk.js.map +11 -0
- package/dist/chunk-fvc7ncxd.js +252 -0
- package/dist/chunk-fvc7ncxd.js.map +10 -0
- package/dist/chunk-g7pvfj5e.js +574 -0
- package/dist/chunk-g7pvfj5e.js.map +14 -0
- package/dist/chunk-h8vmb9gf.js +315 -0
- package/dist/chunk-h8vmb9gf.js.map +10 -0
- package/dist/chunk-hgw7n8cm.js +43 -0
- package/dist/chunk-hgw7n8cm.js.map +10 -0
- package/dist/chunk-hsq7j9jk.js +126 -0
- package/dist/chunk-hsq7j9jk.js.map +10 -0
- package/dist/chunk-hwy3n31w.js +21 -0
- package/dist/chunk-hwy3n31w.js.map +9 -0
- package/dist/chunk-j0kjscb7.js +53 -0
- package/dist/chunk-j0kjscb7.js.map +10 -0
- package/dist/chunk-jzz91jwh.js +70 -0
- package/dist/chunk-jzz91jwh.js.map +10 -0
- package/dist/chunk-k9z47mab.js +247 -0
- package/dist/chunk-k9z47mab.js.map +12 -0
- package/dist/chunk-kbm35t7w.js +1331 -0
- package/dist/chunk-kbm35t7w.js.map +16 -0
- package/dist/chunk-m4veth0c.js +1649 -0
- package/dist/chunk-m4veth0c.js.map +17 -0
- package/dist/chunk-mhty3nna.js +150 -0
- package/dist/chunk-mhty3nna.js.map +11 -0
- package/dist/chunk-mmgh670x.js +248 -0
- package/dist/chunk-mmgh670x.js.map +11 -0
- package/dist/chunk-n0kk5xbq.js +2358 -0
- package/dist/chunk-n0kk5xbq.js.map +26 -0
- package/dist/chunk-nmv41qx9.js +2847 -0
- package/dist/chunk-nmv41qx9.js.map +23 -0
- package/dist/chunk-nqdp52aj.js +113 -0
- package/dist/chunk-nqdp52aj.js.map +12 -0
- package/dist/chunk-ns2m25dz.js +139 -0
- package/dist/chunk-ns2m25dz.js.map +10 -0
- package/dist/chunk-pkdmypdd.js +147 -0
- package/dist/chunk-pkdmypdd.js.map +11 -0
- package/dist/chunk-rmyfmrrr.js +1634 -0
- package/dist/chunk-rmyfmrrr.js.map +15 -0
- package/dist/chunk-sa874j5c.js +9 -0
- package/dist/chunk-sa874j5c.js.map +10 -0
- package/dist/chunk-sgsz2fpq.js +289 -0
- package/dist/chunk-sgsz2fpq.js.map +10 -0
- package/dist/chunk-sn51wnf1.js +3020 -0
- package/dist/chunk-sn51wnf1.js.map +32 -0
- package/dist/chunk-sqhr1a7e.js +587 -0
- package/dist/chunk-sqhr1a7e.js.map +11 -0
- package/dist/chunk-tmj5r2zv.js +2933 -0
- package/dist/chunk-tmj5r2zv.js.map +20 -0
- package/dist/chunk-vefj3880.js +56 -0
- package/dist/chunk-vefj3880.js.map +10 -0
- package/dist/chunk-vp2db42d.js +121 -0
- package/dist/chunk-vp2db42d.js.map +10 -0
- package/dist/chunk-wry79q55.js +108 -0
- package/dist/chunk-wry79q55.js.map +10 -0
- package/dist/chunk-x1arr7ze.js +63 -0
- package/dist/chunk-x1arr7ze.js.map +10 -0
- package/dist/chunk-z2xy5n2q.js +103 -0
- package/dist/chunk-z2xy5n2q.js.map +10 -0
- package/dist/hosted-agent.js +53 -0
- package/dist/hosted-agent.js.map +10 -0
- package/dist/main.js +132 -20
- package/dist/main.js.map +4 -4
- package/dist/runtime/scripts/faster-whisper-wrapper.py +87 -0
- package/dist/runtime/scripts/mcp-patchright-http.mjs +349 -0
- package/dist/runtime/src/agents/api-topic-agent-switch.ts +178 -0
- package/dist/runtime/src/agents/archiver.ts +396 -0
- package/dist/runtime/src/agents/auth-check.ts +64 -0
- package/dist/runtime/src/agents/claude-provider.ts +737 -0
- package/dist/runtime/src/agents/claude-registry.ts +115 -0
- package/dist/runtime/src/agents/codex-native-multi-agent.ts +177 -0
- package/dist/runtime/src/agents/codex-provider.ts +785 -0
- package/dist/runtime/src/agents/codex-registry.ts +115 -0
- package/dist/runtime/src/agents/codex-tree-kill.ts +365 -0
- package/dist/runtime/src/agents/contracts.ts +69 -0
- package/dist/runtime/src/agents/deep-map.ts +13 -0
- package/dist/runtime/src/agents/execution-host.ts +91 -0
- package/dist/runtime/src/agents/fixtures/claude-attachments.jsonl +2 -0
- package/dist/runtime/src/agents/fixtures/codex-shell.jsonl +5 -0
- package/dist/runtime/src/agents/fork.ts +65 -0
- package/dist/runtime/src/agents/hosted-agent.ts +49 -0
- package/dist/runtime/src/agents/idle-archiver.ts +128 -0
- package/dist/runtime/src/agents/index.ts +205 -0
- package/dist/runtime/src/agents/maestro-provider.ts +206 -0
- package/dist/runtime/src/agents/maestro-registry.ts +31 -0
- package/dist/runtime/src/agents/mcp-tools/ask-user.ts +280 -0
- package/dist/runtime/src/agents/mcp-tools/common.ts +21 -0
- package/dist/runtime/src/agents/mcp-tools/self-config.ts +220 -0
- package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +525 -0
- package/dist/runtime/src/agents/mcp-tools/visuals.ts +88 -0
- package/dist/runtime/src/agents/model-catalog.ts +151 -0
- package/dist/runtime/src/agents/registry.ts +24 -0
- package/dist/runtime/src/agents/rollout/claude.ts +331 -0
- package/dist/runtime/src/agents/rollout/codex.ts +569 -0
- package/dist/runtime/src/agents/rollout/shared.ts +205 -0
- package/dist/runtime/src/agents/self-config-core.ts +415 -0
- package/dist/runtime/src/agents/task-events.ts +44 -0
- package/dist/runtime/src/agents/tool-format.ts +352 -0
- package/dist/runtime/src/agents/topic-agent-switch.ts +193 -0
- package/dist/runtime/src/agents/topic-cleanup.ts +231 -0
- package/dist/runtime/src/agents/vault-tool-policy.ts +33 -0
- package/dist/runtime/src/application/execute-external-user-turn.ts +23 -0
- package/dist/runtime/src/application/submit-user-message.ts +50 -0
- package/dist/runtime/src/application/switch-topic-effort.ts +48 -0
- package/dist/runtime/src/application/switch-topic-model.ts +79 -0
- package/dist/runtime/src/application/topic-service.ts +139 -0
- package/dist/runtime/src/application/vault-command.ts +170 -0
- package/dist/runtime/src/bus.ts +319 -0
- package/dist/runtime/src/index.ts +333 -0
- package/dist/runtime/src/mcp/agent-health-server.ts +309 -0
- package/dist/runtime/src/mcp/background-bash-server.ts +346 -0
- package/dist/runtime/src/mcp/canonical-bridge-config.ts +84 -0
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +171 -0
- package/dist/runtime/src/mcp/dm-ipc.ts +60 -0
- package/dist/runtime/src/mcp/mcp-helpers.ts +40 -0
- package/dist/runtime/src/mcp/paddleocr-server.ts +105 -0
- package/dist/runtime/src/mcp/peer-bridge.ts +114 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +137 -0
- package/dist/runtime/src/mcp/session-comm/bridge-ipc-config.ts +27 -0
- package/dist/runtime/src/mcp/session-comm/peer-forward.ts +165 -0
- package/dist/runtime/src/mcp/session-comm/runtime.ts +115 -0
- package/dist/runtime/src/mcp/session-comm/server.ts +649 -0
- package/dist/runtime/src/mcp/session-comm/topic-config.ts +131 -0
- package/dist/runtime/src/mcp/session-comm/topics.ts +229 -0
- package/dist/runtime/src/mcp/stdio-protect.ts +84 -0
- package/dist/runtime/src/mcp/system-health-server.ts +130 -0
- package/dist/runtime/src/mcp/task-server.ts +173 -0
- package/dist/runtime/src/mcp/token-stats-server.ts +98 -0
- package/dist/runtime/src/mcp/vault-http.ts +219 -0
- package/dist/runtime/src/mcp/vault-run.ts +153 -0
- package/dist/runtime/src/mcp/vault-server.ts +104 -0
- package/dist/runtime/src/mcp/wiki-server.ts +755 -0
- package/dist/runtime/src/media/file-events.ts +33 -0
- package/dist/runtime/src/media/text-extractor.ts +312 -0
- package/dist/runtime/src/media/video.ts +92 -0
- package/dist/runtime/src/outbox/file-ops.ts +210 -0
- package/dist/runtime/src/outbox/index.ts +8 -0
- package/dist/runtime/src/outbox/utils.ts +51 -0
- package/dist/runtime/src/platform/background-bash/manager.ts +279 -0
- package/dist/runtime/src/platform/config.ts +371 -0
- package/dist/runtime/src/platform/constants.ts +18 -0
- package/dist/runtime/src/platform/debug-users.ts +14 -0
- package/dist/runtime/src/platform/delay.ts +1 -0
- package/dist/runtime/src/platform/error.ts +9 -0
- package/dist/runtime/src/platform/file-utils.ts +16 -0
- package/dist/runtime/src/platform/jsonl.ts +202 -0
- package/dist/runtime/src/platform/lifecycle.ts +174 -0
- package/dist/runtime/src/platform/logger.ts +31 -0
- package/dist/runtime/src/platform/maestro-bootstrap-env.ts +3 -0
- package/dist/runtime/src/platform/mcp-config.ts +878 -0
- package/dist/runtime/src/platform/modules.ts +145 -0
- package/dist/runtime/src/platform/node-plugins.ts +46 -0
- package/dist/runtime/src/platform/playwright/manager.ts +1233 -0
- package/dist/runtime/src/prompts/agents/wiki-archiver.md +261 -0
- package/dist/runtime/src/prompts/builders.ts +368 -0
- package/dist/runtime/src/prompts/sessions/channel-system.md +48 -0
- package/dist/runtime/src/prompts/sessions/manager-system.md +37 -0
- package/dist/runtime/src/prompts/sessions/topic-system.md +47 -0
- package/dist/runtime/src/prompts/sessions/visual-design.md +86 -0
- package/dist/runtime/src/query/active-rooms.ts +534 -0
- package/dist/runtime/src/query/control.ts +313 -0
- package/dist/runtime/src/query/index.ts +24 -0
- package/dist/runtime/src/query/session-inbox-cleanup.ts +44 -0
- package/dist/runtime/src/query/session-inbox-path.ts +42 -0
- package/dist/runtime/src/query/state.ts +94 -0
- package/dist/runtime/src/query/types.ts +83 -0
- package/dist/runtime/src/runtime/ask-callbacks.ts +153 -0
- package/dist/runtime/src/runtime/attachments.ts +151 -0
- package/dist/runtime/src/runtime/background-sessions.ts +83 -0
- package/dist/runtime/src/runtime/channel-context.ts +107 -0
- package/dist/runtime/src/runtime/errors.ts +69 -0
- package/dist/runtime/src/runtime/event-heartbeat.ts +77 -0
- package/dist/runtime/src/runtime/file-hooks.ts +81 -0
- package/dist/runtime/src/runtime/footer.ts +23 -0
- package/dist/runtime/src/runtime/inbox.ts +946 -0
- package/dist/runtime/src/runtime/tasks.ts +56 -0
- package/dist/runtime/src/runtime/topic-config.ts +29 -0
- package/dist/runtime/src/runtime/turn-runner.ts +2298 -0
- package/dist/runtime/src/runtime/usage-alert.ts +84 -0
- package/dist/runtime/src/runtime/visual-html.ts +109 -0
- package/dist/runtime/src/runtime/visual-store.ts +430 -0
- package/dist/runtime/src/runtime/visuals.ts +171 -0
- package/dist/runtime/src/security/sanitize.ts +27 -0
- package/dist/runtime/src/security/sensitive-path.ts +34 -0
- package/dist/runtime/src/storage/activity-log.ts +152 -0
- package/dist/runtime/src/storage/api-messages.ts +617 -0
- package/dist/runtime/src/storage/api-topic-brief.ts +127 -0
- package/dist/runtime/src/storage/api-topic-config.ts +192 -0
- package/dist/runtime/src/storage/api-topics.ts +844 -0
- package/dist/runtime/src/storage/app-settings.ts +44 -0
- package/dist/runtime/src/storage/browser-profiles.ts +133 -0
- package/dist/runtime/src/storage/conversations.ts +268 -0
- package/dist/runtime/src/storage/forum/agent-settings.ts +20 -0
- package/dist/runtime/src/storage/forum/index.ts +4 -0
- package/dist/runtime/src/storage/forum/repository.ts +283 -0
- package/dist/runtime/src/storage/forum/schema.ts +300 -0
- package/dist/runtime/src/storage/forum-db.ts +23 -0
- package/dist/runtime/src/storage/runtime-events.ts +130 -0
- package/dist/runtime/src/storage/runtime-leases.ts +189 -0
- package/dist/runtime/src/storage/runtime-process-leases.ts +216 -0
- package/dist/runtime/src/storage/runtime-topic-state.ts +148 -0
- package/dist/runtime/src/storage/runtime-turn-requests.ts +301 -0
- package/dist/runtime/src/storage/self-schedules.ts +298 -0
- package/dist/runtime/src/storage/session-asks.ts +347 -0
- package/dist/runtime/src/storage/sqlite.ts +129 -0
- package/dist/runtime/src/storage/tasks.ts +179 -0
- package/dist/runtime/src/storage/token-stats.ts +144 -0
- package/dist/runtime/src/storage/topic-archive-state.ts +59 -0
- package/dist/runtime/src/storage/topic-archive.ts +74 -0
- package/dist/runtime/src/storage/topic-settings.ts +67 -0
- package/dist/runtime/src/storage/topic-transcript.ts +98 -0
- package/dist/runtime/src/storage/vault-crypto.ts +65 -0
- package/dist/runtime/src/storage/vault.ts +225 -0
- package/dist/runtime/src/storage/wiki-summary-names.ts +33 -0
- package/dist/runtime/src/storage/wiki.ts +85 -0
- package/dist/runtime/src/topics/create.ts +123 -0
- package/dist/runtime/src/topics/derive.ts +398 -0
- package/dist/runtime/src/topics/lifecycle.ts +311 -0
- package/dist/runtime/src/topics/links.ts +7 -0
- package/dist/runtime/src/topics/personal-general.ts +65 -0
- package/dist/runtime/src/topics/session.ts +381 -0
- package/dist/runtime/src/types/api.ts +497 -0
- package/dist/runtime/src/types.ts +257 -0
- package/dist/runtime/src/version.ts +1 -0
- package/dist/runtime/tsconfig.json +13 -0
- package/package.json +28 -3
- package/dist/main.d.ts +0 -3
- package/dist/main.d.ts.map +0 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Topic hard-delete with full memory preservation — the single source of
|
|
3
|
+
* truth shared by every host surface (negotium MCP `delete_topic`, CLI, any
|
|
4
|
+
* channel adapter). Ported from otium runtime-api `api/topic-lifecycle.ts`
|
|
5
|
+
* minus peer/cron cascades (deferred subsystems on a standalone node).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { rmSync } from "node:fs";
|
|
9
|
+
import { runArchiverTurn } from "#agents/archiver";
|
|
10
|
+
import { cancelIdleArchiveForTopic } from "#agents/idle-archiver";
|
|
11
|
+
import { cancelSubagentWatchForDeletedTopic } from "#agents/mcp-tools/spawn-subagent";
|
|
12
|
+
import { type PurgeSessionRef, purgeTopicLogs } from "#agents/topic-cleanup";
|
|
13
|
+
import { WsHub } from "#bus";
|
|
14
|
+
import { killBgBash } from "#platform/background-bash/manager";
|
|
15
|
+
import { resolveTopicWorkspaceDir } from "#platform/config";
|
|
16
|
+
import { GENERAL_TOPIC_ID } from "#platform/constants";
|
|
17
|
+
import { delay } from "#platform/delay";
|
|
18
|
+
import { logger } from "#platform/logger";
|
|
19
|
+
import { closeBrowserOwnerTabs, deleteTopicProfileDir } from "#platform/playwright/manager";
|
|
20
|
+
import { abortRoom, getRoomQuery, interSessionQueue } from "#query/active-rooms";
|
|
21
|
+
import { cleanupSessionInboxFiles } from "#query/session-inbox-cleanup";
|
|
22
|
+
import { clearQueryState } from "#query/state";
|
|
23
|
+
import { cancelAskCallbacksForTopic } from "#runtime/ask-callbacks";
|
|
24
|
+
import { deleteFilesForTopic } from "#runtime/file-hooks";
|
|
25
|
+
import { clearQueryUsageAlert } from "#runtime/usage-alert";
|
|
26
|
+
import { deleteTopicVisuals } from "#runtime/visual-store";
|
|
27
|
+
import { deleteMessagesForTopic } from "#storage/api-messages";
|
|
28
|
+
import { deleteTopicBrief } from "#storage/api-topic-brief";
|
|
29
|
+
import { deleteApiTopicConfig } from "#storage/api-topic-config";
|
|
30
|
+
import {
|
|
31
|
+
deleteTopic as deleteTopicDB,
|
|
32
|
+
getTopicMemoryOrigin,
|
|
33
|
+
getTopicSessionId,
|
|
34
|
+
listTopics,
|
|
35
|
+
reparentTopicChildren,
|
|
36
|
+
} from "#storage/api-topics";
|
|
37
|
+
import { getBrowserProfileOwner, getTopicBrowserProfile } from "#storage/browser-profiles";
|
|
38
|
+
import { getRuntimeTurnLease } from "#storage/runtime-leases";
|
|
39
|
+
import { beginRuntimeTopicMaintenance } from "#storage/runtime-topic-state";
|
|
40
|
+
import {
|
|
41
|
+
cancelRuntimeUserTurnRequests,
|
|
42
|
+
cancelRuntimeUserTurnRequestsBeforeEpoch,
|
|
43
|
+
} from "#storage/runtime-turn-requests";
|
|
44
|
+
import { deleteSelfSchedulesForTopic } from "#storage/self-schedules";
|
|
45
|
+
import { deletePendingAsksForTopic } from "#storage/session-asks";
|
|
46
|
+
import { archiveTopicMessages } from "#storage/topic-archive";
|
|
47
|
+
import { deleteTopicArchiveState } from "#storage/topic-archive-state";
|
|
48
|
+
import type { TopicDto } from "#types/api";
|
|
49
|
+
|
|
50
|
+
const DELETE_TURN_WAIT_MS = 5_000;
|
|
51
|
+
|
|
52
|
+
async function abortAndWaitForTopic(topicId: string): Promise<boolean> {
|
|
53
|
+
// Drop first: the dying turn's finally block must not dispatch queued work.
|
|
54
|
+
interSessionQueue.drop(topicId);
|
|
55
|
+
cancelIdleArchiveForTopic(topicId);
|
|
56
|
+
const aborted = abortRoom(topicId);
|
|
57
|
+
if (!aborted) return true;
|
|
58
|
+
|
|
59
|
+
logger.info({ topicId }, "deleteTopicCascade: aborted in-flight turn");
|
|
60
|
+
const deadline = Date.now() + DELETE_TURN_WAIT_MS;
|
|
61
|
+
while ((getRoomQuery(topicId) || getRuntimeTurnLease(topicId)) && Date.now() < deadline) {
|
|
62
|
+
await delay(50);
|
|
63
|
+
}
|
|
64
|
+
if (getRoomQuery(topicId) || getRuntimeTurnLease(topicId)) {
|
|
65
|
+
logger.warn(
|
|
66
|
+
{ topicId, timeoutMs: DELETE_TURN_WAIT_MS },
|
|
67
|
+
"deleteTopicCascade: timed out waiting for in-flight turn cleanup",
|
|
68
|
+
);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function currentSessionRef(topic: TopicDto, sessionId: string | null): PurgeSessionRef[] {
|
|
75
|
+
return topic.agent && sessionId ? [{ agent: topic.agent, sessionId }] : [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function cleanupParticipantResources(
|
|
79
|
+
topic: TopicDto,
|
|
80
|
+
userIds: string[],
|
|
81
|
+
sessionId: string | null,
|
|
82
|
+
cwd: string,
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
for (const [index, participantUserId] of userIds.entries()) {
|
|
85
|
+
try {
|
|
86
|
+
await purgeTopicLogs({
|
|
87
|
+
userId: participantUserId,
|
|
88
|
+
topicName: topic.title,
|
|
89
|
+
cwd,
|
|
90
|
+
// The DB only has one current native session. Its unified-log owner is
|
|
91
|
+
// not knowable after multi-user turns, so include it once as a fallback;
|
|
92
|
+
// every participant manifest is still scanned for older sessions.
|
|
93
|
+
extraSessions: index === 0 ? currentSessionRef(topic, sessionId) : [],
|
|
94
|
+
});
|
|
95
|
+
} catch (err) {
|
|
96
|
+
logger.warn(
|
|
97
|
+
{ err, topicId: topic.id, userId: participantUserId },
|
|
98
|
+
"deleteTopicCascade: participant rollout cleanup failed",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
cleanupSessionInboxFiles(participantUserId, topic.id, topic.title);
|
|
103
|
+
clearQueryState(participantUserId, topic.title);
|
|
104
|
+
clearQueryUsageAlert(participantUserId, topic.id);
|
|
105
|
+
deletePendingAsksForTopic({ userId: participantUserId, topicName: topic.title });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class TopicArchiveRequiredError extends Error {
|
|
110
|
+
readonly code = "TOPIC_ARCHIVE_FAILED";
|
|
111
|
+
readonly topicId: string;
|
|
112
|
+
|
|
113
|
+
constructor(topicId: string, cause: unknown) {
|
|
114
|
+
super("Topic archive failed; deletion was blocked to avoid losing conversation history.");
|
|
115
|
+
this.name = "TopicArchiveRequiredError";
|
|
116
|
+
this.topicId = topicId;
|
|
117
|
+
this.cause = cause;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export class TopicTurnStillActiveError extends Error {
|
|
122
|
+
readonly code = "TOPIC_TURN_STILL_ACTIVE";
|
|
123
|
+
readonly topicId: string;
|
|
124
|
+
|
|
125
|
+
constructor(topicId: string) {
|
|
126
|
+
super(
|
|
127
|
+
"The active turn did not stop in time; deletion was blocked. Use force-delete explicitly.",
|
|
128
|
+
);
|
|
129
|
+
this.name = "TopicTurnStillActiveError";
|
|
130
|
+
this.topicId = topicId;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface DeleteTopicCascadeOptions {
|
|
135
|
+
force?: boolean;
|
|
136
|
+
/** Account deletion may remove that account's otherwise-protected private General. */
|
|
137
|
+
allowManager?: boolean;
|
|
138
|
+
/** Account deletion must not recreate a private General via the archiver. */
|
|
139
|
+
skipArchive?: boolean;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Sequence (memory BEFORE wipe):
|
|
144
|
+
* 1. archiveTopicMessages → dump SQLite conversation to the shared wiki
|
|
145
|
+
* `archive/` as forensic JSONL.
|
|
146
|
+
* 2. runArchiverTurn → background wiki-archiver (summaries/articles/brief).
|
|
147
|
+
* 3. purge topic-owned runtime/filesystem/adapter state.
|
|
148
|
+
* 4. delete messages + config + brief + the topic row.
|
|
149
|
+
*
|
|
150
|
+
* Step 1 is required by default: if the raw archive cannot be written, deletion
|
|
151
|
+
* is blocked to avoid losing conversation history. Pass `force: true` only for
|
|
152
|
+
* an explicit force-delete escape hatch. Step 2 is best-effort because the raw
|
|
153
|
+
* transcript has already been preserved.
|
|
154
|
+
* Caller is responsible for guards (essential-topic / owner / existence).
|
|
155
|
+
*/
|
|
156
|
+
export async function deleteTopicCascade(
|
|
157
|
+
topic: TopicDto,
|
|
158
|
+
userId: string,
|
|
159
|
+
options: DeleteTopicCascadeOptions = {},
|
|
160
|
+
): Promise<void> {
|
|
161
|
+
await deleteTopicCascadeImpl(topic, userId, options, new Set());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function deleteTopicCascadeImpl(
|
|
165
|
+
topic: TopicDto,
|
|
166
|
+
userId: string,
|
|
167
|
+
options: DeleteTopicCascadeOptions,
|
|
168
|
+
deletingAncestorIds: ReadonlySet<string>,
|
|
169
|
+
): Promise<void> {
|
|
170
|
+
const topicId = topic.id;
|
|
171
|
+
if (topicId === GENERAL_TOPIC_ID || (topic.kind === "manager" && !options.allowManager)) {
|
|
172
|
+
logger.warn({ topicId }, "deleteTopicCascade: refused to delete essential topic");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const deletingTopicIds = new Set(deletingAncestorIds);
|
|
176
|
+
deletingTopicIds.add(topicId);
|
|
177
|
+
const force = options.force === true;
|
|
178
|
+
const maintenance = beginRuntimeTopicMaintenance(topicId);
|
|
179
|
+
if (!maintenance) throw new Error("Topic maintenance is already in progress.");
|
|
180
|
+
let deleted = false;
|
|
181
|
+
const cancelledQueryIds = cancelRuntimeUserTurnRequestsBeforeEpoch(topicId, maintenance.epoch);
|
|
182
|
+
for (const queryId of cancelledQueryIds) {
|
|
183
|
+
WsHub.get().broadcastAborted(topicId, queryId, "stopped");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
// Wait for the aborted turn's final session/log writes before taking the
|
|
188
|
+
// forensic snapshot. Normal deletion blocks on timeout; only the explicit
|
|
189
|
+
// force-delete escape hatch may proceed while a provider is still wedged.
|
|
190
|
+
const turnStopped = await abortAndWaitForTopic(topicId);
|
|
191
|
+
if (!turnStopped && !force) throw new TopicTurnStillActiveError(topicId);
|
|
192
|
+
if (!maintenance.isOwned()) throw new Error("Topic maintenance ownership was lost.");
|
|
193
|
+
let archived: ReturnType<typeof archiveTopicMessages> = null;
|
|
194
|
+
|
|
195
|
+
if (!options.skipArchive) {
|
|
196
|
+
try {
|
|
197
|
+
archived = archiveTopicMessages(topicId, topic.title);
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if (!force) {
|
|
200
|
+
logger.warn(
|
|
201
|
+
{ err, topicId },
|
|
202
|
+
"deleteTopicCascade: archive failed - blocking delete unless force=true",
|
|
203
|
+
);
|
|
204
|
+
throw new TopicArchiveRequiredError(topicId, err);
|
|
205
|
+
}
|
|
206
|
+
logger.warn(
|
|
207
|
+
{ err, topicId },
|
|
208
|
+
"deleteTopicCascade: archive failed but force=true - continuing with hard delete",
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (archived) {
|
|
214
|
+
try {
|
|
215
|
+
const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
|
|
216
|
+
runArchiverTurn({
|
|
217
|
+
userId,
|
|
218
|
+
// A memory origin that is also disappearing in this cascade must use
|
|
219
|
+
// file + General memory only. Passing its id to wiki MCP would let the
|
|
220
|
+
// detached archiver recreate an orphan api_topic_brief row later.
|
|
221
|
+
...(memoryTopic.id !== topicId && !deletingTopicIds.has(memoryTopic.id)
|
|
222
|
+
? { topicId: memoryTopic.id }
|
|
223
|
+
: {}),
|
|
224
|
+
topicTitle: memoryTopic.title,
|
|
225
|
+
archivePath: archived.path,
|
|
226
|
+
messageCount: archived.messageCount,
|
|
227
|
+
});
|
|
228
|
+
} catch (err) {
|
|
229
|
+
logger.warn(
|
|
230
|
+
{ err, topicId, archive: archived.path },
|
|
231
|
+
"deleteTopicCascade: background archiver launch failed - raw archive preserved",
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
cancelSubagentWatchForDeletedTopic(topicId);
|
|
237
|
+
|
|
238
|
+
// spawn_subagent rooms are owned by the room that created them. Delete
|
|
239
|
+
// them through the same lifecycle before removing the parent so their
|
|
240
|
+
// active turns, archives, rollouts, and filesystem state are all cleaned
|
|
241
|
+
// up. Other derived rooms (fork/spawn) remain independent and are
|
|
242
|
+
// reparented below as before.
|
|
243
|
+
const spawnedChildren = listTopics().filter(
|
|
244
|
+
(candidate) =>
|
|
245
|
+
candidate.parentTopicId === topicId &&
|
|
246
|
+
candidate.isSubagent &&
|
|
247
|
+
!deletingTopicIds.has(candidate.id),
|
|
248
|
+
);
|
|
249
|
+
for (const child of spawnedChildren) {
|
|
250
|
+
await deleteTopicCascadeImpl(child, userId, options, deletingTopicIds);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const sessionId = getTopicSessionId(topicId);
|
|
254
|
+
const cwd = resolveTopicWorkspaceDir(topicId);
|
|
255
|
+
const participantUserIds = Array.from(
|
|
256
|
+
new Set([userId, ...topic.participants.map((participant) => participant.userId)]),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// Background shells remain topic-owned. Browser profiles are shared, so
|
|
260
|
+
// deletion closes this topic's tabs without deleting login state.
|
|
261
|
+
killBgBash(userId, topicId);
|
|
262
|
+
const browserProfileOwner = getBrowserProfileOwner(topicId, userId);
|
|
263
|
+
const browserProfile = getTopicBrowserProfile(topicId);
|
|
264
|
+
try {
|
|
265
|
+
await closeBrowserOwnerTabs(browserProfileOwner, browserProfile, `topic:${topicId}`);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
logger.warn({ err, topicId }, "deleteTopicCascade: browser owner cleanup failed");
|
|
268
|
+
}
|
|
269
|
+
deleteTopicProfileDir(userId, topicId);
|
|
270
|
+
cancelAskCallbacksForTopic(topicId);
|
|
271
|
+
deleteSelfSchedulesForTopic(topicId);
|
|
272
|
+
|
|
273
|
+
await cleanupParticipantResources(topic, participantUserIds, sessionId, cwd);
|
|
274
|
+
try {
|
|
275
|
+
await deleteFilesForTopic(topicId);
|
|
276
|
+
} catch (err) {
|
|
277
|
+
logger.warn({ err, topicId }, "deleteTopicCascade: host file cleanup failed");
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
281
|
+
} catch (err) {
|
|
282
|
+
logger.warn({ err, topicId, cwd }, "deleteTopicCascade: workspace cleanup failed");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
deleteTopicVisuals(topicId);
|
|
286
|
+
deleteTopicArchiveState(topicId);
|
|
287
|
+
deleteMessagesForTopic(topicId);
|
|
288
|
+
deleteApiTopicConfig(topicId);
|
|
289
|
+
deleteTopicBrief(topicId);
|
|
290
|
+
const reparentedChildIds = reparentTopicChildren(topicId, topic.parentTopicId ?? null);
|
|
291
|
+
const rowDeleted = deleteTopicDB(topicId, { allowManager: options.allowManager });
|
|
292
|
+
if (!rowDeleted) {
|
|
293
|
+
logger.warn({ topicId }, "deleteTopicCascade: topic row was not deleted");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
deleted = true;
|
|
297
|
+
// Mirror the deletion onto the bus so channel adapters (Telegram forum
|
|
298
|
+
// threads, web clients, …) can drop their bindings for the topic.
|
|
299
|
+
WsHub.get().broadcastTopicDeleted(topicId);
|
|
300
|
+
for (const childId of reparentedChildIds) WsHub.get().broadcastTopicUpdated(childId);
|
|
301
|
+
} finally {
|
|
302
|
+
if (deleted) {
|
|
303
|
+
for (const queryId of cancelRuntimeUserTurnRequests(topicId)) {
|
|
304
|
+
WsHub.get().broadcastAborted(topicId, queryId, "stopped");
|
|
305
|
+
}
|
|
306
|
+
maintenance.finish({ deleteState: true });
|
|
307
|
+
} else {
|
|
308
|
+
maintenance.finish();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getRegistry } from "#agents/registry";
|
|
3
|
+
import { resolveDefaultModel } from "#platform/config";
|
|
4
|
+
import { GENERAL_TOPIC_ID } from "#platform/constants";
|
|
5
|
+
import { getApiTopicConfig, setApiTopicConfig } from "#storage/api-topic-config";
|
|
6
|
+
import { getManagerTopicForUser, getTopic, upsertTopic } from "#storage/api-topics";
|
|
7
|
+
import type { TopicDto } from "#types/api";
|
|
8
|
+
|
|
9
|
+
const LEGACY_PERSONAL_GENERAL_DESCRIPTION =
|
|
10
|
+
"나만의 개인 공간이에요. 대화와 AI 작업은 다른 사용자에게 공개되지 않습니다.";
|
|
11
|
+
export const PERSONAL_GENERAL_DESCRIPTION =
|
|
12
|
+
"Your private General. Messages and membership are visible only to you. Workspace memory, wiki, and skills are shared with your workspace.";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Return the caller's private General room, creating it on first use.
|
|
16
|
+
*
|
|
17
|
+
* The legacy `general` row is deliberately excluded: it was shared by every
|
|
18
|
+
* user and remains untouched for an explicit, operator-controlled migration.
|
|
19
|
+
*/
|
|
20
|
+
export function ensurePersonalGeneral(userId: string): TopicDto {
|
|
21
|
+
const existing = getManagerTopicForUser(userId);
|
|
22
|
+
if (existing) {
|
|
23
|
+
if (existing.description === LEGACY_PERSONAL_GENERAL_DESCRIPTION) {
|
|
24
|
+
existing.description = PERSONAL_GENERAL_DESCRIPTION;
|
|
25
|
+
upsertTopic(existing);
|
|
26
|
+
}
|
|
27
|
+
if (!getApiTopicConfig(existing.id)) {
|
|
28
|
+
setApiTopicConfig(existing.id, { model: "deepseek-pro", modelLocked: true });
|
|
29
|
+
}
|
|
30
|
+
return existing;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const now = new Date().toISOString();
|
|
34
|
+
const registry = getRegistry("maestro");
|
|
35
|
+
const topic: TopicDto = {
|
|
36
|
+
id: randomUUID(),
|
|
37
|
+
title: "General",
|
|
38
|
+
description: PERSONAL_GENERAL_DESCRIPTION,
|
|
39
|
+
kind: "manager",
|
|
40
|
+
agent: "maestro",
|
|
41
|
+
defaultModel: resolveDefaultModel("maestro", registry.defaultModel),
|
|
42
|
+
defaultEffort: registry.defaultEffort ?? "medium",
|
|
43
|
+
aiMode: "always",
|
|
44
|
+
aiMention: false,
|
|
45
|
+
participants: [{ userId, role: "owner" }],
|
|
46
|
+
createdAt: now,
|
|
47
|
+
lastMessageAt: now,
|
|
48
|
+
};
|
|
49
|
+
upsertTopic(topic);
|
|
50
|
+
setApiTopicConfig(topic.id, { model: "deepseek-pro", modelLocked: true });
|
|
51
|
+
return topic;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Resolve the retired fixed id for old clients without exposing the shared row. */
|
|
55
|
+
export function resolvePersonalTopicId(topicId: string, userId: string): string {
|
|
56
|
+
return topicId === GENERAL_TOPIC_ID ? ensurePersonalGeneral(userId).id : topicId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isManagerTopic(topicId: string): boolean {
|
|
60
|
+
return getTopic(topicId)?.kind === "manager";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isLegacySharedGeneral(topicId: string): boolean {
|
|
64
|
+
return topicId === GENERAL_TOPIC_ID;
|
|
65
|
+
}
|