negotium 0.1.6 → 0.1.8
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 +2 -2
- package/dist/chunk-0ay6cjdy.js +1331 -0
- package/dist/chunk-0ay6cjdy.js.map +16 -0
- package/dist/chunk-1k7t7bnx.js +97 -0
- package/dist/chunk-1k7t7bnx.js.map +10 -0
- package/dist/chunk-5ahkew9c.js +806 -0
- package/dist/chunk-5ahkew9c.js.map +17 -0
- package/dist/chunk-6cpn7ht2.js +287 -0
- package/dist/chunk-6cpn7ht2.js.map +13 -0
- package/dist/chunk-6kctn20e.js +3529 -0
- package/dist/chunk-6kctn20e.js.map +20 -0
- package/dist/chunk-7xqj7aex.js +77 -0
- package/dist/chunk-7xqj7aex.js.map +11 -0
- package/dist/chunk-9p0ww3tk.js +41 -0
- package/dist/chunk-9p0ww3tk.js.map +10 -0
- package/dist/chunk-a92a7nrk.js +252 -0
- package/dist/chunk-a92a7nrk.js.map +10 -0
- package/dist/chunk-c500xe9z.js +33 -0
- package/dist/chunk-c500xe9z.js.map +10 -0
- package/dist/chunk-cds1qxsf.js +1644 -0
- package/dist/chunk-cds1qxsf.js.map +17 -0
- package/dist/chunk-e4407g8x.js +274 -0
- package/dist/chunk-e4407g8x.js.map +10 -0
- package/dist/chunk-e5pbggsh.js +363 -0
- package/dist/chunk-e5pbggsh.js.map +11 -0
- package/dist/chunk-edb07sjp.js +567 -0
- package/dist/chunk-edb07sjp.js.map +14 -0
- package/dist/chunk-exd0v74a.js +3012 -0
- package/dist/chunk-exd0v74a.js.map +32 -0
- package/dist/chunk-fgrme9tr.js +4398 -0
- package/dist/chunk-fgrme9tr.js.map +47 -0
- package/dist/chunk-fka9eqe1.js +581 -0
- package/dist/chunk-fka9eqe1.js.map +11 -0
- package/dist/chunk-g82jygez.js +108 -0
- package/dist/chunk-g82jygez.js.map +10 -0
- package/dist/chunk-ghs2j1fr.js +312 -0
- package/dist/chunk-ghs2j1fr.js.map +10 -0
- package/dist/chunk-gmy11efb.js +125 -0
- package/dist/chunk-gmy11efb.js.map +10 -0
- package/dist/chunk-gxma4c70.js +110 -0
- package/dist/chunk-gxma4c70.js.map +10 -0
- package/dist/chunk-hkdqhm34.js +61 -0
- package/dist/chunk-hkdqhm34.js.map +10 -0
- package/dist/chunk-hpa0cpzh.js +134 -0
- package/dist/chunk-hpa0cpzh.js.map +10 -0
- package/dist/chunk-hwy3n31w.js +21 -0
- package/dist/chunk-hwy3n31w.js.map +9 -0
- package/dist/chunk-j2behpy7.js +214 -0
- package/dist/chunk-j2behpy7.js.map +10 -0
- package/dist/chunk-jc0at0tc.js +102 -0
- package/dist/chunk-jc0at0tc.js.map +10 -0
- package/dist/chunk-jkqq5ef6.js +243 -0
- package/dist/chunk-jkqq5ef6.js.map +11 -0
- package/dist/chunk-jzkw2f01.js +51 -0
- package/dist/chunk-jzkw2f01.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-n17yxpa8.js +23 -0
- package/dist/chunk-n17yxpa8.js.map +10 -0
- package/dist/chunk-naa09ags.js +2143 -0
- package/dist/chunk-naa09ags.js.map +15 -0
- package/dist/chunk-ns3ke88n.js +142 -0
- package/dist/chunk-ns3ke88n.js.map +10 -0
- package/dist/chunk-p3cr2ejg.js +1626 -0
- package/dist/chunk-p3cr2ejg.js.map +15 -0
- package/dist/chunk-q5bcpf23.js +2925 -0
- package/dist/chunk-q5bcpf23.js.map +20 -0
- package/dist/chunk-rafc55bs.js +424 -0
- package/dist/chunk-rafc55bs.js.map +11 -0
- package/dist/chunk-rbrxjvbj.js +2830 -0
- package/dist/chunk-rbrxjvbj.js.map +23 -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-sm6fh5c5.js +569 -0
- package/dist/chunk-sm6fh5c5.js.map +14 -0
- package/dist/chunk-t9zcg8f1.js +499 -0
- package/dist/chunk-t9zcg8f1.js.map +13 -0
- package/dist/chunk-vp2db42d.js +121 -0
- package/dist/chunk-vp2db42d.js.map +10 -0
- package/dist/chunk-w13jjf4a.js +46 -0
- package/dist/chunk-w13jjf4a.js.map +10 -0
- package/dist/chunk-x1arr7ze.js +63 -0
- package/dist/chunk-x1arr7ze.js.map +10 -0
- package/dist/chunk-xfq68rch.js +583 -0
- package/dist/chunk-xfq68rch.js.map +11 -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 +48 -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 +47 -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 +134 -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 +865 -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 +357 -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 +532 -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 +2286 -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 +300 -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 +255 -0
- package/dist/runtime/src/version.ts +1 -0
- package/dist/runtime/tsconfig.json +13 -0
- package/package.json +16 -3
- package/dist/main.d.ts +0 -3
- package/dist/main.d.ts.map +0 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeVaultKey,
|
|
3
|
+
VAULT_DESCRIPTION_MAX_LENGTH,
|
|
4
|
+
VAULT_VALUE_MAX_BYTES,
|
|
5
|
+
VAULT_VALUE_MIN_BYTES,
|
|
6
|
+
validateVaultKey,
|
|
7
|
+
vaultDel,
|
|
8
|
+
vaultHasKey,
|
|
9
|
+
vaultList,
|
|
10
|
+
vaultSet,
|
|
11
|
+
} from "#storage/vault";
|
|
12
|
+
|
|
13
|
+
export const VAULT_COMMAND_HELP = [
|
|
14
|
+
"Vault commands:",
|
|
15
|
+
"/vault list",
|
|
16
|
+
"/vault set KEY VALUE [description]",
|
|
17
|
+
"/vault set KEY value with spaces | optional description",
|
|
18
|
+
"/vault del KEY",
|
|
19
|
+
"Use {{KEY}} in agent tool requests. Secret values are never shown to the agent.",
|
|
20
|
+
].join("\n");
|
|
21
|
+
|
|
22
|
+
const VAULT_COMMAND_PATTERN = /^\/vault(?:@\w+)?(?:\s|$)/i;
|
|
23
|
+
|
|
24
|
+
export function isVaultCommandLine(commandLine: string): boolean {
|
|
25
|
+
return VAULT_COMMAND_PATTERN.test(commandLine.trim());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseSetValue(rest: string): { value: string; description: string } {
|
|
29
|
+
const pipe = rest.match(/\s+\|\s*/);
|
|
30
|
+
if (pipe?.index !== undefined) {
|
|
31
|
+
return {
|
|
32
|
+
value: rest.slice(0, pipe.index).trimEnd(),
|
|
33
|
+
description: rest.slice(pipe.index + pipe[0].length).trim(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const [value = "", ...description] = rest.trim().split(/\s+/);
|
|
38
|
+
return { value, description: description.join(" ") };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type VaultEntry = { key: string; description: string };
|
|
42
|
+
|
|
43
|
+
export interface SaveVaultEntryResult {
|
|
44
|
+
key: string;
|
|
45
|
+
updated: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function listVaultEntries(userId: string): VaultEntry[] {
|
|
49
|
+
return vaultList(userId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function saveVaultEntry(
|
|
53
|
+
userId: string,
|
|
54
|
+
rawKey: string,
|
|
55
|
+
value: string,
|
|
56
|
+
description = "",
|
|
57
|
+
): SaveVaultEntryResult {
|
|
58
|
+
const key = normalizeVaultKey(rawKey);
|
|
59
|
+
if (!validateVaultKey(key)) {
|
|
60
|
+
throw new Error("Invalid key. Use A-Z, 0-9, and _; start with a letter (max 128 characters).");
|
|
61
|
+
}
|
|
62
|
+
const valueBytes = Buffer.byteLength(value, "utf8");
|
|
63
|
+
if (valueBytes < VAULT_VALUE_MIN_BYTES) {
|
|
64
|
+
throw new Error(`Vault value must be at least ${VAULT_VALUE_MIN_BYTES} bytes.`);
|
|
65
|
+
}
|
|
66
|
+
if (valueBytes > VAULT_VALUE_MAX_BYTES) {
|
|
67
|
+
throw new Error(`Vault value must not exceed ${VAULT_VALUE_MAX_BYTES} bytes.`);
|
|
68
|
+
}
|
|
69
|
+
if (description.length > VAULT_DESCRIPTION_MAX_LENGTH) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Vault description must not exceed ${VAULT_DESCRIPTION_MAX_LENGTH} characters.`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const updated = vaultHasKey(userId, key);
|
|
76
|
+
vaultSet(userId, key, value, description);
|
|
77
|
+
return { key, updated };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function deleteVaultEntry(userId: string, rawKey: string): boolean {
|
|
81
|
+
const key = normalizeVaultKey(rawKey);
|
|
82
|
+
if (!validateVaultKey(key)) {
|
|
83
|
+
throw new Error("Invalid key. Use A-Z, 0-9, and _; start with a letter (max 128 characters).");
|
|
84
|
+
}
|
|
85
|
+
return vaultDel(userId, key);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function renderVaultPanel(entries: VaultEntry[]): string {
|
|
89
|
+
const lines: string[] = [
|
|
90
|
+
"Vault",
|
|
91
|
+
"Store API keys and tokens locally. Use {{KEY}} in agent tool requests.",
|
|
92
|
+
"Secret values are never displayed.",
|
|
93
|
+
"",
|
|
94
|
+
"Keys:",
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
if (entries.length === 0) {
|
|
98
|
+
lines.push("- (empty)");
|
|
99
|
+
} else {
|
|
100
|
+
for (const e of entries) {
|
|
101
|
+
lines.push(e.description ? `- ${e.key}: ${e.description}` : `- ${e.key}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
lines.push(
|
|
106
|
+
"",
|
|
107
|
+
"Commands:",
|
|
108
|
+
"/vault set KEY VALUE | optional description",
|
|
109
|
+
"/vault del KEY",
|
|
110
|
+
"/vault list",
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return lines.join("\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Execute a human-facing Vault command without returning secret plaintext. */
|
|
117
|
+
export function executeVaultCommand(userId: string, commandLine: string): string | null {
|
|
118
|
+
const input = commandLine.trim();
|
|
119
|
+
if (!isVaultCommandLine(input)) return null;
|
|
120
|
+
|
|
121
|
+
const commandMatch = input.match(/^\/vault(?:@\w+)?(?:\s+([^\s]+))?/i);
|
|
122
|
+
const subcommand = commandMatch?.[1]?.toLowerCase();
|
|
123
|
+
|
|
124
|
+
if (!subcommand) {
|
|
125
|
+
const entries = vaultList(userId);
|
|
126
|
+
return renderVaultPanel(entries);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (subcommand === "list") {
|
|
130
|
+
const entries = vaultList(userId);
|
|
131
|
+
if (entries.length === 0) return "Vault is empty.";
|
|
132
|
+
return [
|
|
133
|
+
`Vault keys (${entries.length}):`,
|
|
134
|
+
...entries.map((entry) =>
|
|
135
|
+
entry.description ? `- ${entry.key}: ${entry.description}` : `- ${entry.key}`,
|
|
136
|
+
),
|
|
137
|
+
].join("\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (subcommand === "del") {
|
|
141
|
+
const match = input.match(/^\/vault(?:@\w+)?\s+del\s+(\S+)\s*$/i);
|
|
142
|
+
if (!match?.[1]) return "Usage: /vault del KEY";
|
|
143
|
+
const key = normalizeVaultKey(match[1]);
|
|
144
|
+
if (!validateVaultKey(key)) {
|
|
145
|
+
return "Invalid key. Use A-Z, 0-9, and _; start with a letter (max 128 characters).";
|
|
146
|
+
}
|
|
147
|
+
return deleteVaultEntry(userId, key) ? `Deleted ${key}.` : `No Vault key named ${key}.`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (subcommand === "set") {
|
|
151
|
+
const match = input.match(/^\/vault(?:@\w+)?\s+set\s+(\S+)(?:\s+([\s\S]+))?$/i);
|
|
152
|
+
if (!match?.[1] || !match[2]) return "Usage: /vault set KEY VALUE [description]";
|
|
153
|
+
|
|
154
|
+
const key = normalizeVaultKey(match[1]);
|
|
155
|
+
if (!validateVaultKey(key)) {
|
|
156
|
+
return "Invalid key. Use A-Z, 0-9, and _; start with a letter (max 128 characters).";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const { value, description } = parseSetValue(match[2]);
|
|
160
|
+
if (!value) return "Vault value cannot be empty.";
|
|
161
|
+
try {
|
|
162
|
+
const result = saveVaultEntry(userId, key, value, description);
|
|
163
|
+
return `${result.updated ? "Updated" : "Stored"} ${result.key}.`;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
return error instanceof Error ? error.message : String(error);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return VAULT_COMMAND_HELP;
|
|
170
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuntimeBus — the outbound half of the host boundary.
|
|
3
|
+
*
|
|
4
|
+
* The runtime publishes topic-scoped events here; whatever host embeds the
|
|
5
|
+
* runtime (CLI, Telegram bot, otium worker node, web API) subscribes and
|
|
6
|
+
* renders them on its channel. This replaces otium's WsHub with a
|
|
7
|
+
* channel-agnostic seam: negotium core never knows what a "websocket" or a
|
|
8
|
+
* "chat" is.
|
|
9
|
+
*
|
|
10
|
+
* The default bus is backed by the shared SQLite store. Every process writes
|
|
11
|
+
* events to one monotonic log and tails events written by its peers, while
|
|
12
|
+
* still delivering its own events synchronously. Hosts may replace it with
|
|
13
|
+
* `setRuntimeBus` in tests or specialized embeddings.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { randomUUID } from "node:crypto";
|
|
17
|
+
import type { ToolCallSummaryInput } from "#agents/tool-format";
|
|
18
|
+
import {
|
|
19
|
+
appendRuntimeEvent,
|
|
20
|
+
latestRuntimeEventSeq,
|
|
21
|
+
listRuntimeEventsAfter,
|
|
22
|
+
} from "#storage/runtime-events";
|
|
23
|
+
import type { AgentKind } from "#types";
|
|
24
|
+
import type { MessageDto } from "#types/api";
|
|
25
|
+
|
|
26
|
+
export interface RuntimeBusEvent {
|
|
27
|
+
type:
|
|
28
|
+
| "message" // new message persisted in a topic
|
|
29
|
+
| "message-updated" // existing message patched (edit-in-place progress)
|
|
30
|
+
| "ai-status" // turn started/streaming/finished/aborted
|
|
31
|
+
| "topic-created"
|
|
32
|
+
| "topic-updated" // topic config/title changed
|
|
33
|
+
| "topic-deleted";
|
|
34
|
+
topicId: string;
|
|
35
|
+
payload: unknown;
|
|
36
|
+
/** Durable event-log ordering metadata when available. */
|
|
37
|
+
seq?: number;
|
|
38
|
+
createdAt?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type RuntimeBusListener = (event: RuntimeBusEvent) => void;
|
|
42
|
+
|
|
43
|
+
export interface RuntimeBus {
|
|
44
|
+
broadcastMessage(topicId: string, message: MessageDto): void;
|
|
45
|
+
broadcastMessageUpdated(
|
|
46
|
+
topicId: string,
|
|
47
|
+
messageId: string,
|
|
48
|
+
patch: Partial<MessageDto> & Record<string, unknown>,
|
|
49
|
+
): void;
|
|
50
|
+
broadcastAiStatus(topicId: string, status: Record<string, unknown>): void;
|
|
51
|
+
broadcastTopicCreated(topic: { id: string }): void;
|
|
52
|
+
broadcastTopicUpdated(topicId: string): void;
|
|
53
|
+
broadcastTopicDeleted(topicId: string): void;
|
|
54
|
+
// ── AI turn lifecycle (otium WsHub-compatible surface used by the turn runner) ──
|
|
55
|
+
/** Announce a just-started AI turn so every subscriber learns its queryId. */
|
|
56
|
+
broadcastAiActive(topicId: string, queryId: string): void;
|
|
57
|
+
broadcastDone(
|
|
58
|
+
topicId: string,
|
|
59
|
+
queryId: string,
|
|
60
|
+
usage?: NonNullable<MessageDto["usage"]>,
|
|
61
|
+
meta?: { agent?: AgentKind; model?: string },
|
|
62
|
+
): void;
|
|
63
|
+
broadcastError(topicId: string, queryId: string, error: string): void;
|
|
64
|
+
broadcastAborted(topicId: string, queryId: string, reason?: "superseded" | "stopped"): void;
|
|
65
|
+
broadcastToolCall(
|
|
66
|
+
topicId: string,
|
|
67
|
+
queryId: string,
|
|
68
|
+
name: string,
|
|
69
|
+
input: ToolCallSummaryInput | undefined,
|
|
70
|
+
label: string,
|
|
71
|
+
toolUseId: string,
|
|
72
|
+
): void;
|
|
73
|
+
broadcastToolOutput(topicId: string, queryId: string, toolUseId: string, content: string): void;
|
|
74
|
+
broadcastToolStatus(
|
|
75
|
+
topicId: string,
|
|
76
|
+
queryId: string,
|
|
77
|
+
kind: "status" | "progress" | "summary",
|
|
78
|
+
content: string,
|
|
79
|
+
meta?: { toolName?: string; elapsed?: number },
|
|
80
|
+
): void;
|
|
81
|
+
broadcastFileReady(topicId: string, queryId: string, path: string, source: string): void;
|
|
82
|
+
broadcastReasoning(topicId: string, queryId: string, content: string): void;
|
|
83
|
+
broadcastVisual(
|
|
84
|
+
topicId: string,
|
|
85
|
+
queryId: string,
|
|
86
|
+
url: string,
|
|
87
|
+
id?: number,
|
|
88
|
+
title?: string | null,
|
|
89
|
+
kind?: "html" | "mermaid" | "image" | "video",
|
|
90
|
+
): void;
|
|
91
|
+
/** Typing indicator: userId is the typist ("ai") or "" to clear. */
|
|
92
|
+
broadcastTyping(topicId: string, userId: string): void;
|
|
93
|
+
subscribe(listener: RuntimeBusListener): () => void;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface SqliteRuntimeBusOptions {
|
|
97
|
+
sourceId?: string;
|
|
98
|
+
pollIntervalMs?: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class SqliteRuntimeBus implements RuntimeBus {
|
|
102
|
+
private listeners = new Set<RuntimeBusListener>();
|
|
103
|
+
private readonly sourceId: string;
|
|
104
|
+
private readonly pollIntervalMs: number;
|
|
105
|
+
private cursor: number;
|
|
106
|
+
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
107
|
+
private polling = false;
|
|
108
|
+
|
|
109
|
+
constructor(options: SqliteRuntimeBusOptions = {}) {
|
|
110
|
+
this.sourceId = options.sourceId ?? `${process.pid}-${randomUUID()}`;
|
|
111
|
+
this.pollIntervalMs = Math.max(25, options.pollIntervalMs ?? 100);
|
|
112
|
+
this.cursor = latestRuntimeEventSeq();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private deliver(event: RuntimeBusEvent): void {
|
|
116
|
+
for (const listener of this.listeners) {
|
|
117
|
+
try {
|
|
118
|
+
listener(event);
|
|
119
|
+
} catch {
|
|
120
|
+
// A broken subscriber must never break the runtime.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private emit(event: RuntimeBusEvent): void {
|
|
126
|
+
// Do not jump the read cursor to our insert's seq. Another process may
|
|
127
|
+
// have committed an event immediately before this insert that we have not
|
|
128
|
+
// tailed yet. The poller advances through every seq in order and skips our
|
|
129
|
+
// own rows after the synchronous delivery below.
|
|
130
|
+
const stored = appendRuntimeEvent(this.sourceId, event);
|
|
131
|
+
this.deliver({ ...event, seq: stored.seq, createdAt: stored.createdAt });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private startPolling(): void {
|
|
135
|
+
if (this.pollTimer) return;
|
|
136
|
+
this.pollTimer = setInterval(() => this.poll(), this.pollIntervalMs);
|
|
137
|
+
this.pollTimer.unref?.();
|
|
138
|
+
this.poll();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private stopPolling(): void {
|
|
142
|
+
if (!this.pollTimer) return;
|
|
143
|
+
clearInterval(this.pollTimer);
|
|
144
|
+
this.pollTimer = null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private poll(): void {
|
|
148
|
+
if (this.polling || this.listeners.size === 0) return;
|
|
149
|
+
this.polling = true;
|
|
150
|
+
try {
|
|
151
|
+
while (true) {
|
|
152
|
+
const events = listRuntimeEventsAfter(this.cursor);
|
|
153
|
+
if (events.length === 0) break;
|
|
154
|
+
for (const event of events) {
|
|
155
|
+
this.cursor = Math.max(this.cursor, event.seq);
|
|
156
|
+
if (event.sourceId === this.sourceId) continue;
|
|
157
|
+
this.deliver({
|
|
158
|
+
type: event.type,
|
|
159
|
+
topicId: event.topicId,
|
|
160
|
+
payload: event.payload,
|
|
161
|
+
seq: event.seq,
|
|
162
|
+
createdAt: event.createdAt,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (events.length < 500) break;
|
|
166
|
+
}
|
|
167
|
+
} finally {
|
|
168
|
+
this.polling = false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
broadcastMessage(topicId: string, message: MessageDto): void {
|
|
173
|
+
this.emit({ type: "message", topicId, payload: message });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
broadcastMessageUpdated(
|
|
177
|
+
topicId: string,
|
|
178
|
+
messageId: string,
|
|
179
|
+
patch: Partial<MessageDto> & Record<string, unknown>,
|
|
180
|
+
): void {
|
|
181
|
+
this.emit({ type: "message-updated", topicId, payload: { messageId, patch } });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
broadcastAiStatus(topicId: string, status: Record<string, unknown>): void {
|
|
185
|
+
this.emit({ type: "ai-status", topicId, payload: status });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
broadcastTopicCreated(topic: { id: string }): void {
|
|
189
|
+
this.emit({ type: "topic-created", topicId: topic.id, payload: topic });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
broadcastTopicUpdated(topicId: string): void {
|
|
193
|
+
this.emit({ type: "topic-updated", topicId, payload: null });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
broadcastTopicDeleted(topicId: string): void {
|
|
197
|
+
this.emit({ type: "topic-deleted", topicId, payload: null });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// The turn-lifecycle methods all flow through the "ai-status" channel with a
|
|
201
|
+
// `kind` discriminator mirroring otium's WS message `type` names, so a host
|
|
202
|
+
// can reconstruct the exact WsHub feed from the bus if it wants to.
|
|
203
|
+
broadcastAiActive(topicId: string, queryId: string): void {
|
|
204
|
+
this.broadcastAiStatus(topicId, { kind: "ai_active", queryId });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
broadcastDone(
|
|
208
|
+
topicId: string,
|
|
209
|
+
queryId: string,
|
|
210
|
+
usage?: NonNullable<MessageDto["usage"]>,
|
|
211
|
+
meta?: { agent?: AgentKind; model?: string },
|
|
212
|
+
): void {
|
|
213
|
+
this.broadcastAiStatus(topicId, {
|
|
214
|
+
kind: "ai_done",
|
|
215
|
+
queryId,
|
|
216
|
+
usage,
|
|
217
|
+
agent: meta?.agent,
|
|
218
|
+
model: meta?.model,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
broadcastError(topicId: string, queryId: string, error: string): void {
|
|
223
|
+
this.broadcastAiStatus(topicId, { kind: "ai_error", queryId, error });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
broadcastAborted(topicId: string, queryId: string, reason?: "superseded" | "stopped"): void {
|
|
227
|
+
this.broadcastAiStatus(topicId, { kind: "ai_aborted", queryId, reason });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
broadcastToolCall(
|
|
231
|
+
topicId: string,
|
|
232
|
+
queryId: string,
|
|
233
|
+
name: string,
|
|
234
|
+
input: ToolCallSummaryInput | undefined,
|
|
235
|
+
label: string,
|
|
236
|
+
toolUseId: string,
|
|
237
|
+
): void {
|
|
238
|
+
const id = toolUseId.trim() || `${queryId}:tool`;
|
|
239
|
+
this.broadcastAiStatus(topicId, {
|
|
240
|
+
kind: "tool_call",
|
|
241
|
+
queryId,
|
|
242
|
+
name,
|
|
243
|
+
input,
|
|
244
|
+
label,
|
|
245
|
+
toolUseId: id,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
broadcastToolOutput(topicId: string, queryId: string, toolUseId: string, content: string): void {
|
|
250
|
+
const id = toolUseId.trim() || `${queryId}:tool`;
|
|
251
|
+
this.broadcastAiStatus(topicId, { kind: "tool_output", queryId, toolUseId: id, content });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
broadcastToolStatus(
|
|
255
|
+
topicId: string,
|
|
256
|
+
queryId: string,
|
|
257
|
+
kind: "status" | "progress" | "summary",
|
|
258
|
+
content: string,
|
|
259
|
+
meta?: { toolName?: string; elapsed?: number },
|
|
260
|
+
): void {
|
|
261
|
+
this.broadcastAiStatus(topicId, {
|
|
262
|
+
kind: "tool_status",
|
|
263
|
+
queryId,
|
|
264
|
+
statusKind: kind,
|
|
265
|
+
content,
|
|
266
|
+
toolName: meta?.toolName,
|
|
267
|
+
elapsed: meta?.elapsed,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
broadcastFileReady(topicId: string, queryId: string, path: string, source: string): void {
|
|
272
|
+
this.broadcastAiStatus(topicId, { kind: "file_ready", queryId, path, source });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
broadcastReasoning(topicId: string, queryId: string, content: string): void {
|
|
276
|
+
this.broadcastAiStatus(topicId, { kind: "reasoning", queryId, content });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
broadcastVisual(
|
|
280
|
+
topicId: string,
|
|
281
|
+
queryId: string,
|
|
282
|
+
url: string,
|
|
283
|
+
id?: number,
|
|
284
|
+
title?: string | null,
|
|
285
|
+
kind?: "html" | "mermaid" | "image" | "video",
|
|
286
|
+
): void {
|
|
287
|
+
this.broadcastAiStatus(topicId, { kind: "visual", queryId, url, id, title, visualKind: kind });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
broadcastTyping(topicId: string, userId: string): void {
|
|
291
|
+
this.broadcastAiStatus(topicId, { kind: "typing", userId });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
subscribe(listener: RuntimeBusListener): () => void {
|
|
295
|
+
this.listeners.add(listener);
|
|
296
|
+
this.startPolling();
|
|
297
|
+
return () => {
|
|
298
|
+
this.listeners.delete(listener);
|
|
299
|
+
if (this.listeners.size === 0) this.stopPolling();
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let current: RuntimeBus = new SqliteRuntimeBus();
|
|
305
|
+
|
|
306
|
+
export function runtimeBus(): RuntimeBus {
|
|
307
|
+
return current;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function setRuntimeBus(bus: RuntimeBus): void {
|
|
311
|
+
current = bus;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Drop-in compat for code ported from otium's `WsHub.get()` call sites. */
|
|
315
|
+
export const WsHub = {
|
|
316
|
+
get(): RuntimeBus {
|
|
317
|
+
return current;
|
|
318
|
+
},
|
|
319
|
+
};
|