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,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derived-topic creation — fork (config+history copy), spawn (config-only),
|
|
3
|
+
* and subagent (agent-initiated worker room) share this helper.
|
|
4
|
+
*
|
|
5
|
+
* Ported from otium runtime-api `api/routes/topics.ts`, minus the REST route
|
|
6
|
+
* table; the route-facing wrappers (`getTopics`, `updateTopic`) live here too
|
|
7
|
+
* so ported call sites keep working.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { mkdirSync } from "node:fs";
|
|
12
|
+
import { cleanupAgentFork, type ForkHandle, forkAgentSession } from "#agents/fork";
|
|
13
|
+
import { resolveModelForAgent } from "#agents/model-catalog";
|
|
14
|
+
import { getRegistry } from "#agents/registry";
|
|
15
|
+
import { WsHub } from "#bus";
|
|
16
|
+
import { resolveTopicWorkspaceDir } from "#platform/config";
|
|
17
|
+
import { logger } from "#platform/logger";
|
|
18
|
+
import { cloneProfileForChild } from "#platform/playwright/manager";
|
|
19
|
+
import { isTopicRunning } from "#query/active-rooms";
|
|
20
|
+
import { copyMessagesForTopic } from "#storage/api-messages";
|
|
21
|
+
import { getApiTopicConfig, setApiTopicConfig } from "#storage/api-topic-config";
|
|
22
|
+
import {
|
|
23
|
+
findTopicTitleConflict,
|
|
24
|
+
getTopic,
|
|
25
|
+
getTopicSessionId,
|
|
26
|
+
inferTopicKind,
|
|
27
|
+
isTopicVisible,
|
|
28
|
+
listTopics,
|
|
29
|
+
setTopicSessionId,
|
|
30
|
+
upsertTopic,
|
|
31
|
+
} from "#storage/api-topics";
|
|
32
|
+
import {
|
|
33
|
+
appendConversationEventStrict,
|
|
34
|
+
cloneConversationLog,
|
|
35
|
+
readConversation,
|
|
36
|
+
} from "#storage/conversations";
|
|
37
|
+
import { db } from "#storage/forum-db";
|
|
38
|
+
import { beginRuntimeTopicMaintenance } from "#storage/runtime-topic-state";
|
|
39
|
+
import { isLegacySharedGeneral } from "#topics/personal-general";
|
|
40
|
+
import type { AgentKind } from "#types";
|
|
41
|
+
import type { TopicDto } from "#types/api";
|
|
42
|
+
|
|
43
|
+
export function getTopics(): TopicDto[] {
|
|
44
|
+
return listTopics().filter((topic) => !isLegacySharedGeneral(topic.id));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Topics adapters may show in lists and selection UIs. */
|
|
48
|
+
export function getVisibleTopics(): TopicDto[] {
|
|
49
|
+
return getTopics()
|
|
50
|
+
.filter(isTopicVisible)
|
|
51
|
+
.map((topic) => {
|
|
52
|
+
if (!topic.agent) return topic;
|
|
53
|
+
const registry = getRegistry(topic.agent);
|
|
54
|
+
const config = getApiTopicConfig(topic.id);
|
|
55
|
+
const requestedEffort = config?.effort ?? topic.defaultEffort;
|
|
56
|
+
return {
|
|
57
|
+
...topic,
|
|
58
|
+
effectiveModel: resolveModelForAgent(
|
|
59
|
+
topic.agent,
|
|
60
|
+
config?.model ?? topic.defaultModel,
|
|
61
|
+
registry,
|
|
62
|
+
),
|
|
63
|
+
effectiveEffort:
|
|
64
|
+
requestedEffort && registry.validateEffort(requestedEffort)
|
|
65
|
+
? requestedEffort
|
|
66
|
+
: registry.defaultEffort,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function updateTopic(topicId: string, patch: Partial<TopicDto>): boolean {
|
|
72
|
+
const topic = getTopic(topicId);
|
|
73
|
+
if (!topic) return false;
|
|
74
|
+
Object.assign(topic, patch);
|
|
75
|
+
upsertTopic(topic);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isParticipant(topic: TopicDto, userId: string): boolean {
|
|
80
|
+
return topic.participants.some((p) => p.userId === userId);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function nextDerivedTopicTitle(
|
|
84
|
+
sourceTitle: string,
|
|
85
|
+
kind: TopicDto["kind"],
|
|
86
|
+
suffix: "fork" | "spawn" | "agent",
|
|
87
|
+
): string {
|
|
88
|
+
const visibleTitles = new Set(
|
|
89
|
+
listTopics()
|
|
90
|
+
.filter((topic) => topic.kind === kind)
|
|
91
|
+
.map((topic) => topic.title.toLowerCase()),
|
|
92
|
+
);
|
|
93
|
+
let n = 1;
|
|
94
|
+
let title = `${sourceTitle}-${suffix}-${n}`;
|
|
95
|
+
while (visibleTitles.has(title.toLowerCase())) {
|
|
96
|
+
n += 1;
|
|
97
|
+
title = `${sourceTitle}-${suffix}-${n}`;
|
|
98
|
+
}
|
|
99
|
+
return title;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function effectiveAgentForTopic(_topicId: string, topic: TopicDto): AgentKind | undefined {
|
|
103
|
+
if (!topic.agent) return undefined;
|
|
104
|
+
return topic.agent;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function rollbackHandleFor(agent: AgentKind, sessionId: string, rolloutPath: string): ForkHandle {
|
|
108
|
+
return { agent, forkId: sessionId, rolloutPath };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Thrown when a user-supplied derived-topic name collides with an existing
|
|
112
|
+
* topic title. Callers surface this as a specific "pick a different name"
|
|
113
|
+
* message instead of the generic membership/session failure. */
|
|
114
|
+
export class TopicTitleConflictError extends Error {
|
|
115
|
+
constructor(readonly title: string) {
|
|
116
|
+
super(`A topic named "${title}" already exists`);
|
|
117
|
+
this.name = "TopicTitleConflictError";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export class TopicDeriveBusyError extends Error {
|
|
122
|
+
constructor(message = "Topic is busy; try again when the current operation finishes") {
|
|
123
|
+
super(message);
|
|
124
|
+
this.name = "TopicDeriveBusyError";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface DerivedTopicOptions {
|
|
129
|
+
name?: string;
|
|
130
|
+
subagent?: { agent?: AgentKind; model?: string };
|
|
131
|
+
allowActiveSource?: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Shared helper for spawn (config-only copy) and fork (config+history copy).
|
|
136
|
+
*
|
|
137
|
+
* - fork (copyHistory=true): inherits ALL source participants, copies messages,
|
|
138
|
+
* creator becomes owner, and forks the source AI session when AI is enabled.
|
|
139
|
+
* - spawn (copyHistory=false): config only, creator is sole owner, empty history.
|
|
140
|
+
* It still creates a fresh AI session when AI is enabled.
|
|
141
|
+
* - subagent (copyHistory=false + opts.subagent): agent-initiated worker room.
|
|
142
|
+
* Marked `isSubagent`, optionally overriding the child's agent/model.
|
|
143
|
+
*
|
|
144
|
+
* @param sourceTopicId - must exist and caller must be a participant
|
|
145
|
+
* @param userId - caller (becomes owner of the new topic)
|
|
146
|
+
* @param copyHistory - true for fork, false for spawn
|
|
147
|
+
* @param opts - optional custom name and subagent overrides
|
|
148
|
+
* @returns the newly created TopicDto or null on error
|
|
149
|
+
* @throws TopicDeriveBusyError when user-triggered derivation races active work or maintenance
|
|
150
|
+
* @throws TopicTitleConflictError when the requested name is already taken
|
|
151
|
+
*/
|
|
152
|
+
export async function createDerivedTopic(
|
|
153
|
+
sourceTopicId: string,
|
|
154
|
+
userId: string,
|
|
155
|
+
copyHistory: boolean,
|
|
156
|
+
opts?: DerivedTopicOptions,
|
|
157
|
+
): Promise<TopicDto | null> {
|
|
158
|
+
const topic = getTopic(sourceTopicId);
|
|
159
|
+
if (!topic) return null;
|
|
160
|
+
if (topic.kind === "manager") return null;
|
|
161
|
+
if (!isParticipant(topic, userId)) return null;
|
|
162
|
+
|
|
163
|
+
const subagent = copyHistory ? undefined : opts?.subagent;
|
|
164
|
+
let maintenance: ReturnType<typeof beginRuntimeTopicMaintenance> = null;
|
|
165
|
+
if (!subagent && !opts?.allowActiveSource) {
|
|
166
|
+
if (isTopicRunning(sourceTopicId)) throw new TopicDeriveBusyError();
|
|
167
|
+
maintenance = beginRuntimeTopicMaintenance(sourceTopicId);
|
|
168
|
+
if (!maintenance) throw new TopicDeriveBusyError();
|
|
169
|
+
if (isTopicRunning(sourceTopicId)) {
|
|
170
|
+
maintenance.finish();
|
|
171
|
+
maintenance = null;
|
|
172
|
+
throw new TopicDeriveBusyError();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
return await createDerivedTopicUnderFence(topic, sourceTopicId, userId, copyHistory, opts);
|
|
178
|
+
} finally {
|
|
179
|
+
maintenance?.finish();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function createDerivedTopicUnderFence(
|
|
184
|
+
topic: TopicDto,
|
|
185
|
+
sourceTopicId: string,
|
|
186
|
+
userId: string,
|
|
187
|
+
copyHistory: boolean,
|
|
188
|
+
opts?: DerivedTopicOptions,
|
|
189
|
+
): Promise<TopicDto | null> {
|
|
190
|
+
const now = new Date().toISOString();
|
|
191
|
+
const subagent = copyHistory ? undefined : opts?.subagent;
|
|
192
|
+
const suffix = copyHistory ? "fork" : subagent ? "agent" : "spawn";
|
|
193
|
+
const sourceConfig = getApiTopicConfig(sourceTopicId);
|
|
194
|
+
const agent = subagent?.agent ?? effectiveAgentForTopic(sourceTopicId, topic);
|
|
195
|
+
|
|
196
|
+
// Fork: inherit all source participants, creator becomes owner
|
|
197
|
+
// Spawn: creator is sole owner
|
|
198
|
+
// Subagent: inherit all source participants — everyone who can see the
|
|
199
|
+
// parent room sees the card there, so its "view room" target must be
|
|
200
|
+
// accessible to them too (single-topic GET rejects non-participants).
|
|
201
|
+
const participants: TopicDto["participants"] =
|
|
202
|
+
copyHistory || subagent
|
|
203
|
+
? [
|
|
204
|
+
...topic.participants
|
|
205
|
+
.filter((p) => p.userId !== userId)
|
|
206
|
+
.map((p) => ({ ...p, role: "member" as const })),
|
|
207
|
+
{ userId, role: "owner" as const },
|
|
208
|
+
]
|
|
209
|
+
: [{ userId, role: "owner" as const }];
|
|
210
|
+
const kind = topic.kind ?? inferTopicKind(topic);
|
|
211
|
+
const title = opts?.name?.trim() || nextDerivedTopicTitle(topic.title, kind, suffix);
|
|
212
|
+
const conflict = findTopicTitleConflict(title, kind);
|
|
213
|
+
if (conflict) {
|
|
214
|
+
logger.info(
|
|
215
|
+
{ sourceTopicId, title, kind, conflictTopicId: conflict.id },
|
|
216
|
+
"createDerivedTopic: title conflict",
|
|
217
|
+
);
|
|
218
|
+
throw new TopicTitleConflictError(title);
|
|
219
|
+
}
|
|
220
|
+
const derived: TopicDto = {
|
|
221
|
+
id: randomUUID(),
|
|
222
|
+
title,
|
|
223
|
+
kind,
|
|
224
|
+
description: topic.description,
|
|
225
|
+
agent: subagent?.agent ?? topic.agent,
|
|
226
|
+
defaultModel: subagent?.model ?? topic.defaultModel,
|
|
227
|
+
defaultEffort: topic.defaultEffort,
|
|
228
|
+
aiMode: topic.aiMode,
|
|
229
|
+
aiMention: topic.aiMention,
|
|
230
|
+
participants,
|
|
231
|
+
createdAt: now,
|
|
232
|
+
lastMessageAt: now,
|
|
233
|
+
parentTopicId: sourceTopicId,
|
|
234
|
+
isFork: copyHistory,
|
|
235
|
+
...(subagent ? { isSubagent: true } : {}),
|
|
236
|
+
visibility: topic.visibility,
|
|
237
|
+
accessMode: topic.accessMode,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
let sessionId: string | undefined;
|
|
241
|
+
let rollbackHandle: ForkHandle | undefined;
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
if (agent) {
|
|
245
|
+
const cwd = resolveTopicWorkspaceDir(derived.id);
|
|
246
|
+
mkdirSync(cwd, { recursive: true });
|
|
247
|
+
const registry = getRegistry(agent);
|
|
248
|
+
const requestedRolloutModel =
|
|
249
|
+
subagent?.model ??
|
|
250
|
+
(subagent?.agent ? undefined : sourceConfig?.model) ??
|
|
251
|
+
derived.defaultModel;
|
|
252
|
+
const rolloutModel = resolveModelForAgent(agent, requestedRolloutModel, registry);
|
|
253
|
+
const requestedRolloutEffort = subagent?.agent
|
|
254
|
+
? registry.defaultEffort
|
|
255
|
+
: (sourceConfig?.effort ?? derived.defaultEffort);
|
|
256
|
+
const rolloutEffort =
|
|
257
|
+
requestedRolloutEffort && registry.validateEffort(requestedRolloutEffort)
|
|
258
|
+
? requestedRolloutEffort
|
|
259
|
+
: registry.defaultEffort;
|
|
260
|
+
|
|
261
|
+
if (copyHistory) {
|
|
262
|
+
const parentSessionId = getTopicSessionId(sourceTopicId);
|
|
263
|
+
if (parentSessionId) {
|
|
264
|
+
try {
|
|
265
|
+
const fork = await forkAgentSession({
|
|
266
|
+
agent,
|
|
267
|
+
parentSessionId,
|
|
268
|
+
cwd,
|
|
269
|
+
userId,
|
|
270
|
+
topicName: topic.title,
|
|
271
|
+
title,
|
|
272
|
+
model: rolloutModel,
|
|
273
|
+
...(rolloutEffort ? { effort: rolloutEffort } : {}),
|
|
274
|
+
});
|
|
275
|
+
sessionId = fork.forkId;
|
|
276
|
+
rollbackHandle = fork;
|
|
277
|
+
} catch (err) {
|
|
278
|
+
logger.warn(
|
|
279
|
+
{ err, sourceTopicId, title: topic.title, agent, parentSessionId },
|
|
280
|
+
"createDerivedTopic: native fork failed; synthesizing from unified history",
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (!sessionId) {
|
|
285
|
+
const rollout = registry.writeRollout({
|
|
286
|
+
cwd,
|
|
287
|
+
entries: readConversation(userId, topic.title),
|
|
288
|
+
model: rolloutModel,
|
|
289
|
+
...(rolloutEffort ? { effort: rolloutEffort } : {}),
|
|
290
|
+
});
|
|
291
|
+
sessionId = rollout.sessionId;
|
|
292
|
+
rollbackHandle = rollbackHandleFor(agent, rollout.sessionId, rollout.rolloutPath);
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
const rollout = registry.writeRollout({
|
|
296
|
+
cwd,
|
|
297
|
+
entries: [],
|
|
298
|
+
model: rolloutModel,
|
|
299
|
+
...(rolloutEffort ? { effort: rolloutEffort } : {}),
|
|
300
|
+
});
|
|
301
|
+
sessionId = rollout.sessionId;
|
|
302
|
+
rollbackHandle = rollbackHandleFor(agent, rollout.sessionId, rollout.rolloutPath);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const created = db
|
|
307
|
+
.transaction(() => {
|
|
308
|
+
const currentSource = getTopic(sourceTopicId);
|
|
309
|
+
if (
|
|
310
|
+
!currentSource ||
|
|
311
|
+
currentSource.kind === "manager" ||
|
|
312
|
+
!isParticipant(currentSource, userId)
|
|
313
|
+
) {
|
|
314
|
+
throw new TopicDeriveBusyError("Source topic changed while deriving; try again");
|
|
315
|
+
}
|
|
316
|
+
const transactionalConflict = findTopicTitleConflict(title, kind);
|
|
317
|
+
if (transactionalConflict) throw new TopicTitleConflictError(title);
|
|
318
|
+
upsertTopic(derived);
|
|
319
|
+
if (subagent) {
|
|
320
|
+
// Subagent worker rooms do NOT cascade the parent's optional MCP
|
|
321
|
+
// whitelist — they start with the default (required-only) MCP set,
|
|
322
|
+
// mirroring clawgram's subagent policy. An explicit agent/model
|
|
323
|
+
// override must also beat the parent's config override, which
|
|
324
|
+
// startAiTurn resolves ahead of the topic defaults; dropping the
|
|
325
|
+
// inherited model when only the agent changes avoids pinning the
|
|
326
|
+
// child to a model that belongs to the parent's agent.
|
|
327
|
+
const childConfig = { ...(sourceConfig ?? {}) };
|
|
328
|
+
delete childConfig.mcp;
|
|
329
|
+
if (subagent.agent) {
|
|
330
|
+
delete childConfig.model;
|
|
331
|
+
}
|
|
332
|
+
if (subagent.model) childConfig.model = subagent.model;
|
|
333
|
+
if (Object.keys(childConfig).length > 0) setApiTopicConfig(derived.id, childConfig);
|
|
334
|
+
} else if (sourceConfig) {
|
|
335
|
+
setApiTopicConfig(derived.id, sourceConfig);
|
|
336
|
+
}
|
|
337
|
+
if (copyHistory) {
|
|
338
|
+
copyMessagesForTopic(sourceTopicId, derived.id);
|
|
339
|
+
cloneConversationLog({
|
|
340
|
+
userId,
|
|
341
|
+
srcTopic: topic.title,
|
|
342
|
+
dstTopic: derived.title,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (sessionId && agent) {
|
|
346
|
+
setTopicSessionId(derived.id, sessionId, {
|
|
347
|
+
reason: copyHistory ? "slash-fork" : subagent ? "spawn-subagent" : "slash-spawn",
|
|
348
|
+
agent,
|
|
349
|
+
});
|
|
350
|
+
appendConversationEventStrict(userId, derived.title, agent, {
|
|
351
|
+
type: "session",
|
|
352
|
+
sessionId,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
return derived;
|
|
356
|
+
})
|
|
357
|
+
.immediate();
|
|
358
|
+
|
|
359
|
+
// Derived rooms inherit the browser profile only when the creator already
|
|
360
|
+
// owns the source. Crossing an owner boundary always starts with a fresh
|
|
361
|
+
// profile so cookies and login state cannot be transferred by a member.
|
|
362
|
+
try {
|
|
363
|
+
const profileClone = await cloneProfileForChild({
|
|
364
|
+
userId,
|
|
365
|
+
srcTopic: sourceTopicId,
|
|
366
|
+
dstTopic: derived.id,
|
|
367
|
+
});
|
|
368
|
+
logger.info(
|
|
369
|
+
{
|
|
370
|
+
sourceTopicId,
|
|
371
|
+
derivedTopicId: derived.id,
|
|
372
|
+
copyHistory,
|
|
373
|
+
subagent: Boolean(subagent),
|
|
374
|
+
profileClone,
|
|
375
|
+
},
|
|
376
|
+
"createDerivedTopic: playwright profile clone",
|
|
377
|
+
);
|
|
378
|
+
} catch (profileErr) {
|
|
379
|
+
logger.warn(
|
|
380
|
+
{ err: profileErr, sourceTopicId, derivedTopicId: derived.id },
|
|
381
|
+
"createDerivedTopic: playwright profile clone threw (continuing)",
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Every derived room (fork/spawn/subagent) is user-visible: tell all
|
|
386
|
+
// inherited participants, not just the client that issued the request.
|
|
387
|
+
WsHub.get().broadcastTopicCreated(created);
|
|
388
|
+
return created;
|
|
389
|
+
} catch (err) {
|
|
390
|
+
if (rollbackHandle) cleanupAgentFork(rollbackHandle);
|
|
391
|
+
if (err instanceof TopicDeriveBusyError || err instanceof TopicTitleConflictError) throw err;
|
|
392
|
+
logger.warn(
|
|
393
|
+
{ err, sourceTopicId, derivedTopicId: derived.id, copyHistory },
|
|
394
|
+
"createDerivedTopic: failed to create derived topic",
|
|
395
|
+
);
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
}
|