@tenex-chat/backend 0.9.4 → 0.9.6
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 +5 -1
- package/dist/daemon-wrapper.cjs +47 -0
- package/dist/index.js +59268 -0
- package/dist/wrapper.js +171 -0
- package/package.json +19 -27
- package/src/agents/AgentRegistry.ts +9 -7
- package/src/agents/AgentStorage.ts +24 -1
- package/src/agents/agent-installer.ts +6 -0
- package/src/agents/agent-loader.ts +7 -2
- package/src/agents/constants.ts +10 -2
- package/src/agents/execution/AgentExecutor.ts +35 -6
- package/src/agents/execution/StreamCallbacks.ts +53 -13
- package/src/agents/execution/StreamExecutionHandler.ts +110 -16
- package/src/agents/execution/StreamSetup.ts +19 -9
- package/src/agents/execution/ToolEventHandlers.ts +112 -0
- package/src/agents/role-categories.ts +53 -0
- package/src/agents/types/runtime.ts +7 -0
- package/src/agents/types/storage.ts +7 -0
- package/src/commands/agent/import/openclaw-distiller.ts +63 -7
- package/src/commands/agent/import/openclaw-reader.ts +54 -0
- package/src/commands/agent/import/openclaw.ts +120 -29
- package/src/commands/agent/index.ts +83 -2
- package/src/commands/setup/display.ts +123 -0
- package/src/commands/setup/embed.ts +13 -13
- package/src/commands/setup/global-system-prompt.ts +15 -17
- package/src/commands/setup/image.ts +17 -20
- package/src/commands/setup/interactive.ts +37 -20
- package/src/commands/setup/llm.ts +12 -7
- package/src/commands/setup/onboarding.ts +1580 -248
- package/src/commands/setup/providers.ts +3 -3
- package/src/conversations/ConversationStore.ts +23 -2
- package/src/conversations/MessageBuilder.ts +51 -73
- package/src/conversations/formatters/utils/conversation-transcript-formatter.ts +425 -0
- package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +40 -98
- package/src/conversations/search/embeddings/ConversationIndexingJob.ts +40 -52
- package/src/conversations/services/ConversationSummarizer.ts +1 -2
- package/src/conversations/types.ts +11 -0
- package/src/daemon/Daemon.ts +78 -57
- package/src/daemon/ProjectRuntime.ts +6 -12
- package/src/daemon/SubscriptionManager.ts +13 -0
- package/src/daemon/index.ts +0 -1
- package/src/event-handler/index.ts +1 -0
- package/src/index.ts +20 -1
- package/src/llm/ChunkHandler.ts +1 -1
- package/src/llm/FinishHandler.ts +28 -4
- package/src/llm/LLMConfigEditor.ts +218 -106
- package/src/llm/index.ts +0 -4
- package/src/llm/meta/MetaModelResolver.ts +3 -18
- package/src/llm/middleware/message-sanitizer.ts +153 -0
- package/src/llm/providers/ollama-models.ts +0 -38
- package/src/llm/service.ts +50 -15
- package/src/llm/types.ts +0 -12
- package/src/llm/utils/ConfigurationManager.ts +88 -465
- package/src/llm/utils/ConfigurationTester.ts +42 -185
- package/src/llm/utils/ModelSelector.ts +156 -92
- package/src/llm/utils/ProviderConfigUI.ts +10 -141
- package/src/llm/utils/models-dev-cache.ts +102 -23
- package/src/llm/utils/provider-select-prompt.ts +284 -0
- package/src/llm/utils/provider-setup.ts +81 -34
- package/src/llm/utils/variant-list-prompt.ts +361 -0
- package/src/nostr/AgentEventDecoder.ts +1 -0
- package/src/nostr/AgentEventEncoder.ts +37 -0
- package/src/nostr/AgentProfilePublisher.ts +13 -0
- package/src/nostr/AgentPublisher.ts +26 -0
- package/src/nostr/kinds.ts +1 -0
- package/src/nostr/ndkClient.ts +4 -1
- package/src/nostr/types.ts +12 -0
- package/src/prompts/fragments/25-rag-instructions.ts +22 -21
- package/src/prompts/fragments/31-agents-md-guidance.ts +7 -21
- package/src/prompts/fragments/index.ts +2 -0
- package/src/prompts/utils/systemPromptBuilder.ts +18 -28
- package/src/services/AgentDefinitionMonitor.ts +8 -0
- package/src/services/ConfigService.ts +34 -0
- package/src/services/PubkeyService.ts +7 -1
- package/src/services/compression/CompressionService.ts +133 -74
- package/src/services/compression/compression-utils.ts +110 -19
- package/src/services/config/types.ts +0 -6
- package/src/services/dispatch/AgentDispatchService.ts +79 -0
- package/src/services/intervention/InterventionService.ts +78 -5
- package/src/services/nip46/Nip46SigningService.ts +30 -1
- package/src/services/projects/ProjectContext.ts +8 -6
- package/src/services/rag/RAGCollectionRegistry.ts +199 -0
- package/src/services/rag/RAGDatabaseService.ts +2 -7
- package/src/services/rag/RAGOperations.ts +25 -45
- package/src/services/rag/RAGService.ts +0 -31
- package/src/services/rag/RagSubscriptionService.ts +71 -122
- package/src/services/rag/rag-utils.ts +13 -0
- package/src/services/ral/RALRegistry.ts +25 -184
- package/src/services/reports/ReportEmbeddingService.ts +63 -113
- package/src/services/search/UnifiedSearchService.ts +115 -4
- package/src/services/search/index.ts +1 -0
- package/src/services/search/projectFilter.ts +20 -4
- package/src/services/search/providers/ConversationSearchProvider.ts +1 -0
- package/src/services/search/providers/GenericCollectionSearchProvider.ts +81 -0
- package/src/services/search/providers/LessonSearchProvider.ts +1 -8
- package/src/services/search/providers/ReportSearchProvider.ts +1 -0
- package/src/services/search/types.ts +24 -3
- package/src/services/trust-pubkeys/SystemPubkeyListService.ts +148 -0
- package/src/services/trust-pubkeys/TrustPubkeyService.ts +70 -9
- package/src/telemetry/setup.ts +2 -13
- package/src/tools/implementations/ask.ts +3 -3
- package/src/tools/implementations/conversation_get.ts +28 -268
- package/src/tools/implementations/fs_grep.ts +6 -6
- package/src/tools/implementations/fs_read.ts +2 -0
- package/src/tools/implementations/fs_write.ts +2 -0
- package/src/tools/implementations/learn.ts +38 -50
- package/src/tools/implementations/rag_add_documents.ts +6 -4
- package/src/tools/implementations/rag_create_collection.ts +37 -4
- package/src/tools/implementations/rag_delete_collection.ts +9 -0
- package/src/tools/implementations/{search.ts → rag_search.ts} +31 -25
- package/src/tools/registry.ts +7 -8
- package/src/tools/types.ts +11 -2
- package/src/tools/utils/transcript-args.ts +13 -0
- package/src/utils/cli-theme.ts +13 -0
- package/src/utils/logger.ts +55 -0
- package/src/utils/metadataKeys.ts +17 -0
- package/src/utils/sqlEscaping.ts +39 -0
- package/src/wrapper.ts +7 -3
- package/dist/src/index.js +0 -46778
- package/dist/tenex-backend-wrapper.cjs +0 -3
- package/src/agents/execution/constants.ts +0 -16
- package/src/agents/execution/index.ts +0 -3
- package/src/agents/index.ts +0 -4
- package/src/commands/agent.ts +0 -215
- package/src/conversations/formatters/DelegationXmlFormatter.ts +0 -64
- package/src/conversations/formatters/index.ts +0 -9
- package/src/conversations/index.ts +0 -2
- package/src/conversations/utils/content-utils.ts +0 -69
- package/src/daemon/UnixSocketTransport.ts +0 -318
- package/src/event-handler/newConversation.ts +0 -165
- package/src/events/NDKProjectStatus.ts +0 -384
- package/src/events/index.ts +0 -4
- package/src/lib/json-parser.ts +0 -30
- package/src/llm/RecordingState.ts +0 -37
- package/src/llm/StreamPublisher.ts +0 -40
- package/src/llm/middleware/flight-recorder.ts +0 -188
- package/src/llm/utils/claudeCodePromptCompiler.ts +0 -141
- package/src/nostr/constants.ts +0 -38
- package/src/prompts/core/index.ts +0 -3
- package/src/prompts/index.ts +0 -21
- package/src/services/image/index.ts +0 -12
- package/src/services/status/index.ts +0 -11
- package/src/telemetry/diagnostics.ts +0 -27
- package/src/tools/implementations/rag_query.ts +0 -107
- package/src/types/index.ts +0 -46
- package/src/utils/agentFetcher.ts +0 -107
- package/src/utils/conversation-utils.ts +0 -1
- package/src/utils/process.ts +0 -49
|
@@ -1,50 +1,97 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import password from "@inquirer/password";
|
|
2
|
+
import input from "@inquirer/input";
|
|
3
|
+
import chalk from "chalk";
|
|
3
4
|
import { AI_SDK_PROVIDERS } from "@/llm/types";
|
|
4
5
|
import type { ProviderCredentials, TenexProviders } from "@/services/config/types";
|
|
5
|
-
import {
|
|
6
|
+
import { PROVIDER_IDS } from "@/llm/providers/provider-ids";
|
|
7
|
+
import providerSelectPrompt, {
|
|
8
|
+
getKeys,
|
|
9
|
+
isOllama,
|
|
10
|
+
type PromptState,
|
|
11
|
+
type ProviderSelectConfig,
|
|
12
|
+
} from "@/llm/utils/provider-select-prompt";
|
|
6
13
|
import { ProviderConfigUI } from "@/llm/utils/ProviderConfigUI";
|
|
14
|
+
import { inquirerTheme } from "@/utils/cli-theme";
|
|
15
|
+
|
|
16
|
+
interface ProviderSetupOptions {
|
|
17
|
+
providerHints?: Record<string, string>;
|
|
18
|
+
}
|
|
7
19
|
|
|
8
20
|
/**
|
|
9
21
|
* Interactive flow for configuring provider credentials.
|
|
10
|
-
*
|
|
22
|
+
* Uses a two-level prompt: a provider list (browse/keys) and a separate
|
|
23
|
+
* password prompt for entering API keys.
|
|
11
24
|
*/
|
|
12
25
|
export async function runProviderSetup(
|
|
13
|
-
existingProviders: TenexProviders
|
|
26
|
+
existingProviders: TenexProviders,
|
|
27
|
+
options: ProviderSetupOptions = {},
|
|
14
28
|
): Promise<TenexProviders> {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
const providerIds = AI_SDK_PROVIDERS.filter((p) => p !== PROVIDER_IDS.CLAUDE_CODE);
|
|
30
|
+
const { providerHints } = options;
|
|
31
|
+
|
|
32
|
+
let resumeState: PromptState | undefined;
|
|
33
|
+
|
|
34
|
+
while (true) {
|
|
35
|
+
const baseConfig: ProviderSelectConfig = {
|
|
36
|
+
message: "Configure providers:",
|
|
37
|
+
providerIds: [...providerIds],
|
|
38
|
+
initialProviders: { ...existingProviders.providers },
|
|
39
|
+
providerHints,
|
|
40
|
+
resumeState,
|
|
41
|
+
theme: inquirerTheme,
|
|
25
42
|
};
|
|
26
|
-
});
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
choices,
|
|
34
|
-
},
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
if (!selected || selected.length === 0) {
|
|
38
|
-
return existingProviders;
|
|
39
|
-
}
|
|
44
|
+
const result = await providerSelectPrompt(baseConfig);
|
|
45
|
+
|
|
46
|
+
if (result.action === "done") {
|
|
47
|
+
return { providers: result.providers };
|
|
48
|
+
}
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
// add-key: ask for the key via a separate prompt
|
|
51
|
+
const { providerId, returnTo, state } = result;
|
|
52
|
+
const name = ProviderConfigUI.getProviderDisplayName(providerId);
|
|
53
|
+
const apiKey = await askForKey(providerId, name, providerHints?.[providerId]);
|
|
54
|
+
|
|
55
|
+
if (apiKey) {
|
|
56
|
+
const existing = getKeys(state.providers[providerId]?.apiKey);
|
|
57
|
+
if (existing.length > 0) {
|
|
58
|
+
state.providers[providerId] = {
|
|
59
|
+
...state.providers[providerId],
|
|
60
|
+
apiKey: [...existing, apiKey],
|
|
61
|
+
} as ProviderCredentials;
|
|
62
|
+
} else {
|
|
63
|
+
state.providers[providerId] = { apiKey };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Restore the prompt in the mode we came from
|
|
68
|
+
resumeState = {
|
|
69
|
+
...state,
|
|
70
|
+
mode: returnTo,
|
|
71
|
+
keysTarget: returnTo === "keys" ? providerId : null,
|
|
72
|
+
keysActive: 0,
|
|
46
73
|
};
|
|
47
74
|
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function askForKey(providerId: string, displayName: string, hint?: string): Promise<string | undefined> {
|
|
78
|
+
if (isOllama(providerId)) {
|
|
79
|
+
const url = await input({
|
|
80
|
+
message: `${displayName} URL:`,
|
|
81
|
+
default: "http://localhost:11434",
|
|
82
|
+
theme: inquirerTheme,
|
|
83
|
+
});
|
|
84
|
+
return url.trim() || undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (hint) {
|
|
88
|
+
console.log(chalk.dim(` Run ${chalk.bold("claude setup-token")} in another terminal, then paste the key (sk-ant-...) here.`));
|
|
89
|
+
}
|
|
48
90
|
|
|
49
|
-
|
|
91
|
+
const key = await password({
|
|
92
|
+
message: `${displayName} API key:`,
|
|
93
|
+
mask: "*",
|
|
94
|
+
theme: inquirerTheme,
|
|
95
|
+
});
|
|
96
|
+
return key.trim() || undefined;
|
|
50
97
|
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPrompt,
|
|
3
|
+
useState,
|
|
4
|
+
useKeypress,
|
|
5
|
+
usePrefix,
|
|
6
|
+
isUpKey,
|
|
7
|
+
isDownKey,
|
|
8
|
+
isEnterKey,
|
|
9
|
+
isBackspaceKey,
|
|
10
|
+
makeTheme,
|
|
11
|
+
type Theme,
|
|
12
|
+
} from "@inquirer/core";
|
|
13
|
+
import type { PartialDeep } from "@inquirer/type";
|
|
14
|
+
import { cursorHide } from "@inquirer/ansi";
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import inquirer from "inquirer";
|
|
17
|
+
import { inquirerTheme } from "@/utils/cli-theme";
|
|
18
|
+
import type { MetaModelConfiguration, MetaModelVariant } from "@/services/config/types";
|
|
19
|
+
import * as display from "@/commands/setup/display";
|
|
20
|
+
|
|
21
|
+
type VariantListAction =
|
|
22
|
+
| { action: "edit"; variantName: string }
|
|
23
|
+
| { action: "add" }
|
|
24
|
+
| { action: "done" };
|
|
25
|
+
|
|
26
|
+
type VariantListResult = VariantListAction & {
|
|
27
|
+
variants: Record<string, MetaModelVariant>;
|
|
28
|
+
defaultVariant: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type VariantListConfig = {
|
|
32
|
+
message: string;
|
|
33
|
+
variants: Record<string, MetaModelVariant>;
|
|
34
|
+
defaultVariant: string;
|
|
35
|
+
theme?: PartialDeep<Theme>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const variantListRawPrompt = createPrompt<VariantListResult, VariantListConfig>(
|
|
39
|
+
(config, done) => {
|
|
40
|
+
const theme = makeTheme(config.theme);
|
|
41
|
+
const prefix = usePrefix({ status: "idle", theme });
|
|
42
|
+
|
|
43
|
+
const [active, setActive] = useState(0);
|
|
44
|
+
const [variants, setVariants] = useState<Record<string, MetaModelVariant>>(
|
|
45
|
+
() => ({ ...config.variants }),
|
|
46
|
+
);
|
|
47
|
+
const [defaultVariant, setDefaultVariant] = useState(config.defaultVariant);
|
|
48
|
+
|
|
49
|
+
const variantNames = Object.keys(variants);
|
|
50
|
+
const addIndex = variantNames.length;
|
|
51
|
+
const doneIndex = variantNames.length + 1;
|
|
52
|
+
const itemCount = variantNames.length + 2;
|
|
53
|
+
|
|
54
|
+
useKeypress((key, rl) => {
|
|
55
|
+
rl.clearLine(0);
|
|
56
|
+
|
|
57
|
+
if (isUpKey(key)) {
|
|
58
|
+
setActive(Math.max(0, active - 1));
|
|
59
|
+
} else if (isDownKey(key)) {
|
|
60
|
+
setActive(Math.min(itemCount - 1, active + 1));
|
|
61
|
+
} else if (isEnterKey(key)) {
|
|
62
|
+
if (active < variantNames.length) {
|
|
63
|
+
const name = variantNames[active];
|
|
64
|
+
if (name) {
|
|
65
|
+
done({
|
|
66
|
+
action: "edit",
|
|
67
|
+
variantName: name,
|
|
68
|
+
variants: { ...variants },
|
|
69
|
+
defaultVariant,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
} else if (active === addIndex) {
|
|
73
|
+
done({
|
|
74
|
+
action: "add",
|
|
75
|
+
variants: { ...variants },
|
|
76
|
+
defaultVariant,
|
|
77
|
+
});
|
|
78
|
+
} else if (active === doneIndex) {
|
|
79
|
+
if (variantNames.length < 2) return;
|
|
80
|
+
done({
|
|
81
|
+
action: "done",
|
|
82
|
+
variants: { ...variants },
|
|
83
|
+
defaultVariant,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
} else if (key.name === "d" && active < variantNames.length) {
|
|
87
|
+
const name = variantNames[active];
|
|
88
|
+
if (name) setDefaultVariant(name);
|
|
89
|
+
} else if (
|
|
90
|
+
(isBackspaceKey(key) || key.name === "delete") &&
|
|
91
|
+
active < variantNames.length
|
|
92
|
+
) {
|
|
93
|
+
if (variantNames.length <= 2) return;
|
|
94
|
+
|
|
95
|
+
const nameToDelete = variantNames[active];
|
|
96
|
+
if (!nameToDelete) return;
|
|
97
|
+
|
|
98
|
+
const updated = { ...variants };
|
|
99
|
+
delete updated[nameToDelete];
|
|
100
|
+
setVariants(updated);
|
|
101
|
+
|
|
102
|
+
if (defaultVariant === nameToDelete) {
|
|
103
|
+
const remaining = Object.keys(updated);
|
|
104
|
+
setDefaultVariant(remaining[0] ?? "");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const newCount = Object.keys(updated).length;
|
|
108
|
+
if (active >= newCount) {
|
|
109
|
+
setActive(Math.max(0, newCount - 1));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Render
|
|
115
|
+
const cursor = chalk.hex("#FFC107")("›");
|
|
116
|
+
const lines: string[] = [];
|
|
117
|
+
|
|
118
|
+
lines.push(`${prefix} ${theme.style.message(config.message, "idle")}`);
|
|
119
|
+
lines.push("");
|
|
120
|
+
lines.push(chalk.dim(" Variants:"));
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < variantNames.length; i++) {
|
|
123
|
+
const name = variantNames[i]!;
|
|
124
|
+
const variant = variants[name]!;
|
|
125
|
+
const isDefault = name === defaultVariant;
|
|
126
|
+
const pfx = i === active ? `${cursor} ` : " ";
|
|
127
|
+
const defaultTag = isDefault ? chalk.dim(" (default)") : "";
|
|
128
|
+
const modelDisplay = chalk.gray(`[${variant.model}]`);
|
|
129
|
+
|
|
130
|
+
lines.push(`${pfx}${name} ${modelDisplay}${defaultTag}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
lines.push(` ${"─".repeat(40)}`);
|
|
134
|
+
|
|
135
|
+
// Add variant
|
|
136
|
+
const addPfx = active === addIndex ? `${cursor} ` : " ";
|
|
137
|
+
lines.push(`${addPfx}${chalk.cyan("Add variant")}`);
|
|
138
|
+
|
|
139
|
+
// Done
|
|
140
|
+
const donePfx = active === doneIndex ? `${cursor} ` : " ";
|
|
141
|
+
if (variantNames.length < 2) {
|
|
142
|
+
lines.push(`${donePfx}${chalk.dim("Done (need at least 2 variants)")}`);
|
|
143
|
+
} else {
|
|
144
|
+
lines.push(`${donePfx}${display.doneLabel()}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Help line
|
|
148
|
+
const helpParts = [
|
|
149
|
+
`${chalk.bold("↑↓")} ${chalk.dim("navigate")}`,
|
|
150
|
+
`${chalk.bold("⏎")} ${chalk.dim("edit")}`,
|
|
151
|
+
`${chalk.bold("d")} ${chalk.dim("set default")}`,
|
|
152
|
+
`${chalk.bold("⌫")} ${chalk.dim("remove")}`,
|
|
153
|
+
];
|
|
154
|
+
lines.push(chalk.dim(` ${helpParts.join(chalk.dim(" • "))}`));
|
|
155
|
+
|
|
156
|
+
return `${lines.join("\n")}${cursorHide}`;
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
async function editVariantDetail(
|
|
161
|
+
variantName: string,
|
|
162
|
+
state: { variants: Record<string, MetaModelVariant>; defaultVariant: string },
|
|
163
|
+
standardConfigs: string[],
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
const variant = state.variants[variantName];
|
|
166
|
+
if (!variant) return;
|
|
167
|
+
|
|
168
|
+
while (true) {
|
|
169
|
+
const isDefault = variantName === state.defaultVariant;
|
|
170
|
+
const defaultTag = isDefault ? " (default)" : "";
|
|
171
|
+
|
|
172
|
+
display.blank();
|
|
173
|
+
display.context(`Variant: ${variantName} → ${variant.model}${defaultTag}`);
|
|
174
|
+
display.blank();
|
|
175
|
+
|
|
176
|
+
const { field } = await inquirer.prompt([{
|
|
177
|
+
type: "select",
|
|
178
|
+
name: "field",
|
|
179
|
+
message: `Edit ${variantName}:`,
|
|
180
|
+
choices: [
|
|
181
|
+
{
|
|
182
|
+
name: `Model ${chalk.dim(variant.model)}`,
|
|
183
|
+
value: "model",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: `Trigger keyword ${chalk.dim(variant.keywords?.join(", ") || "(none)")}`,
|
|
187
|
+
value: "keywords",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: `When to use ${chalk.dim(variant.description || "(none)")}`,
|
|
191
|
+
value: "description",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: `Behavior when active ${chalk.dim(variant.systemPrompt || "(none)")}`,
|
|
195
|
+
value: "systemPrompt",
|
|
196
|
+
description: "Extra instructions given to the agent when this variant is selected, e.g. 'Reason step by step'",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "Back",
|
|
200
|
+
value: "back",
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
theme: inquirerTheme,
|
|
204
|
+
}]);
|
|
205
|
+
|
|
206
|
+
if (field === "back") break;
|
|
207
|
+
|
|
208
|
+
if (field === "model") {
|
|
209
|
+
const { model } = await inquirer.prompt([{
|
|
210
|
+
type: "select",
|
|
211
|
+
name: "model",
|
|
212
|
+
message: "Select model:",
|
|
213
|
+
choices: standardConfigs.map((n) => ({ name: n, value: n })),
|
|
214
|
+
theme: inquirerTheme,
|
|
215
|
+
}]);
|
|
216
|
+
variant.model = model;
|
|
217
|
+
} else if (field === "keywords") {
|
|
218
|
+
const { keywordsInput } = await inquirer.prompt([{
|
|
219
|
+
type: "input",
|
|
220
|
+
name: "keywordsInput",
|
|
221
|
+
message: "Trigger keywords (comma-separated):",
|
|
222
|
+
default: variant.keywords?.join(", ") || "",
|
|
223
|
+
theme: inquirerTheme,
|
|
224
|
+
}]);
|
|
225
|
+
const keywords = keywordsInput
|
|
226
|
+
? keywordsInput.split(",").map((k: string) => k.trim().toLowerCase()).filter((k: string) => k.length > 0)
|
|
227
|
+
: [];
|
|
228
|
+
if (keywords.length > 0) {
|
|
229
|
+
variant.keywords = keywords;
|
|
230
|
+
} else {
|
|
231
|
+
delete variant.keywords;
|
|
232
|
+
}
|
|
233
|
+
} else if (field === "description") {
|
|
234
|
+
const { desc } = await inquirer.prompt([{
|
|
235
|
+
type: "input",
|
|
236
|
+
name: "desc",
|
|
237
|
+
message: "When to use this variant:",
|
|
238
|
+
default: variant.description || "",
|
|
239
|
+
theme: inquirerTheme,
|
|
240
|
+
}]);
|
|
241
|
+
if (desc) {
|
|
242
|
+
variant.description = desc;
|
|
243
|
+
} else {
|
|
244
|
+
delete variant.description;
|
|
245
|
+
}
|
|
246
|
+
} else if (field === "systemPrompt") {
|
|
247
|
+
const { prompt } = await inquirer.prompt([{
|
|
248
|
+
type: "input",
|
|
249
|
+
name: "prompt",
|
|
250
|
+
message: "Behavior when active:",
|
|
251
|
+
default: variant.systemPrompt || "",
|
|
252
|
+
theme: inquirerTheme,
|
|
253
|
+
}]);
|
|
254
|
+
if (prompt) {
|
|
255
|
+
variant.systemPrompt = prompt;
|
|
256
|
+
} else {
|
|
257
|
+
delete variant.systemPrompt;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function addVariant(
|
|
264
|
+
state: { variants: Record<string, MetaModelVariant>; defaultVariant: string },
|
|
265
|
+
standardConfigs: string[],
|
|
266
|
+
): Promise<void> {
|
|
267
|
+
const { name } = await inquirer.prompt([{
|
|
268
|
+
type: "input",
|
|
269
|
+
name: "name",
|
|
270
|
+
message: "Variant name:",
|
|
271
|
+
validate: (input: string) => {
|
|
272
|
+
if (!input.trim()) return "Name is required";
|
|
273
|
+
if (state.variants[input]) return "Variant already exists";
|
|
274
|
+
return true;
|
|
275
|
+
},
|
|
276
|
+
theme: inquirerTheme,
|
|
277
|
+
}]);
|
|
278
|
+
|
|
279
|
+
const { model } = await inquirer.prompt([{
|
|
280
|
+
type: "select",
|
|
281
|
+
name: "model",
|
|
282
|
+
message: "Select model for this variant:",
|
|
283
|
+
choices: standardConfigs.map((n) => ({ name: n, value: n })),
|
|
284
|
+
theme: inquirerTheme,
|
|
285
|
+
}]);
|
|
286
|
+
|
|
287
|
+
const isFirst = Object.keys(state.variants).length === 0;
|
|
288
|
+
|
|
289
|
+
state.variants[name] = { model };
|
|
290
|
+
|
|
291
|
+
// First variant auto-becomes default
|
|
292
|
+
if (!state.defaultVariant || isFirst) {
|
|
293
|
+
state.defaultVariant = name;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// For non-first variants, ask "when to use" so the system prompt is useful
|
|
297
|
+
if (!isFirst) {
|
|
298
|
+
const { desc } = await inquirer.prompt([{
|
|
299
|
+
type: "input",
|
|
300
|
+
name: "desc",
|
|
301
|
+
message: "When to use this variant:",
|
|
302
|
+
theme: inquirerTheme,
|
|
303
|
+
}]);
|
|
304
|
+
if (desc) {
|
|
305
|
+
state.variants[name]!.description = desc;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Interactive variant list prompt for multi-modal configuration.
|
|
312
|
+
* Shows variants in a navigable list with add/edit/delete/set-default actions.
|
|
313
|
+
* Returns a complete MetaModelConfiguration when done.
|
|
314
|
+
*/
|
|
315
|
+
export async function variantListPrompt(
|
|
316
|
+
configName: string,
|
|
317
|
+
standardConfigs: string[],
|
|
318
|
+
): Promise<MetaModelConfiguration> {
|
|
319
|
+
let variants: Record<string, MetaModelVariant> = {};
|
|
320
|
+
let defaultVariant = "";
|
|
321
|
+
|
|
322
|
+
// No variants yet — go straight to adding the first one
|
|
323
|
+
const initialState = { variants, defaultVariant };
|
|
324
|
+
await addVariant(initialState, standardConfigs);
|
|
325
|
+
variants = initialState.variants;
|
|
326
|
+
defaultVariant = initialState.defaultVariant;
|
|
327
|
+
|
|
328
|
+
while (true) {
|
|
329
|
+
const result = await variantListRawPrompt({
|
|
330
|
+
message: configName,
|
|
331
|
+
variants,
|
|
332
|
+
defaultVariant,
|
|
333
|
+
theme: inquirerTheme,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
variants = result.variants;
|
|
337
|
+
defaultVariant = result.defaultVariant;
|
|
338
|
+
|
|
339
|
+
if (result.action === "done") {
|
|
340
|
+
return {
|
|
341
|
+
provider: "meta",
|
|
342
|
+
variants,
|
|
343
|
+
default: defaultVariant,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (result.action === "edit" && result.variantName) {
|
|
348
|
+
const state = { variants, defaultVariant };
|
|
349
|
+
await editVariantDetail(result.variantName, state, standardConfigs);
|
|
350
|
+
variants = state.variants;
|
|
351
|
+
defaultVariant = state.defaultVariant;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (result.action === "add") {
|
|
355
|
+
const state = { variants, defaultVariant };
|
|
356
|
+
await addVariant(state, standardConfigs);
|
|
357
|
+
variants = state.variants;
|
|
358
|
+
defaultVariant = state.defaultVariant;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
EventContext,
|
|
18
18
|
InterventionReviewIntent,
|
|
19
19
|
LessonIntent,
|
|
20
|
+
StreamTextDeltaIntent,
|
|
20
21
|
ToolUseIntent,
|
|
21
22
|
} from "./types";
|
|
22
23
|
|
|
@@ -561,6 +562,42 @@ export class AgentEventEncoder {
|
|
|
561
562
|
return event;
|
|
562
563
|
}
|
|
563
564
|
|
|
565
|
+
/**
|
|
566
|
+
* Encode an ephemeral stream text-delta event.
|
|
567
|
+
* These events are best-effort live updates and do not replace kind:1 snapshots.
|
|
568
|
+
*/
|
|
569
|
+
encodeStreamTextDelta(intent: StreamTextDeltaIntent, context: EventContext): NDKEvent {
|
|
570
|
+
const event = new NDKEvent(getNDK());
|
|
571
|
+
event.kind = NDKKind.TenexStreamTextDelta;
|
|
572
|
+
event.content = intent.delta;
|
|
573
|
+
|
|
574
|
+
// Keep thread association via root conversation e-tag.
|
|
575
|
+
this.addConversationTags(event, context);
|
|
576
|
+
|
|
577
|
+
// Required project association for project-scoped filtering.
|
|
578
|
+
this.aTagProject(event);
|
|
579
|
+
|
|
580
|
+
// Include model when available for diagnostics/client metadata.
|
|
581
|
+
if (context.model) {
|
|
582
|
+
const modelString =
|
|
583
|
+
typeof context.model === "string"
|
|
584
|
+
? context.model
|
|
585
|
+
: (context.model as { model?: string }).model;
|
|
586
|
+
if (modelString) {
|
|
587
|
+
event.tag(["llm-model", modelString]);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Preserve RAL and strict delta ordering for client reconstruction.
|
|
592
|
+
event.tag(["llm-ral", context.ralNumber.toString()]);
|
|
593
|
+
event.tag(["stream-seq", intent.sequence.toString()]);
|
|
594
|
+
|
|
595
|
+
// Forward branch tag when present to preserve worktree context.
|
|
596
|
+
this.forwardBranchTag(event, context);
|
|
597
|
+
|
|
598
|
+
return event;
|
|
599
|
+
}
|
|
600
|
+
|
|
564
601
|
/**
|
|
565
602
|
* Encode an intervention review request event.
|
|
566
603
|
* This is used by the InterventionService when a user hasn't responded
|
|
@@ -4,6 +4,7 @@ import { NDKKind } from "@/nostr/kinds";
|
|
|
4
4
|
import { getNDK } from "@/nostr/ndkClient";
|
|
5
5
|
import { config } from "@/services/ConfigService";
|
|
6
6
|
import { Nip46SigningService, Nip46SigningLog } from "@/services/nip46";
|
|
7
|
+
import { getSystemPubkeyListService } from "@/services/trust-pubkeys/SystemPubkeyListService";
|
|
7
8
|
import { logger } from "@/utils/logger";
|
|
8
9
|
import {
|
|
9
10
|
NDKEvent,
|
|
@@ -291,6 +292,10 @@ export class AgentProfilePublisher {
|
|
|
291
292
|
let profileEvent: NDKEvent;
|
|
292
293
|
|
|
293
294
|
try {
|
|
295
|
+
await getSystemPubkeyListService().syncWhitelistFile({
|
|
296
|
+
additionalPubkeys: [signer.pubkey, ...(whitelistedPubkeys ?? [])],
|
|
297
|
+
});
|
|
298
|
+
|
|
294
299
|
// Check if there are other agents with the same slug (name) in this project
|
|
295
300
|
// If so, append pubkey prefix for disambiguation
|
|
296
301
|
const projectDTag = projectEvent.dTag;
|
|
@@ -493,6 +498,10 @@ export class AgentProfilePublisher {
|
|
|
493
498
|
whitelistedPubkeys?: string[]
|
|
494
499
|
): Promise<void> {
|
|
495
500
|
try {
|
|
501
|
+
await getSystemPubkeyListService().syncWhitelistFile({
|
|
502
|
+
additionalPubkeys: [signer.pubkey, ...(whitelistedPubkeys ?? [])],
|
|
503
|
+
});
|
|
504
|
+
|
|
496
505
|
const avatarUrl = AgentProfilePublisher.buildAvatarUrl(signer.pubkey);
|
|
497
506
|
|
|
498
507
|
const profile = {
|
|
@@ -583,6 +592,10 @@ export class AgentProfilePublisher {
|
|
|
583
592
|
projectTitle: string
|
|
584
593
|
): Promise<void> {
|
|
585
594
|
try {
|
|
595
|
+
await getSystemPubkeyListService().syncWhitelistFile({
|
|
596
|
+
additionalPubkeys: [signer.pubkey],
|
|
597
|
+
});
|
|
598
|
+
|
|
586
599
|
// Hash-based deduplication: skip if instructions haven't changed
|
|
587
600
|
const instructionHash = crypto
|
|
588
601
|
.createHash("sha256")
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ErrorIntent,
|
|
17
17
|
EventContext,
|
|
18
18
|
LessonIntent,
|
|
19
|
+
StreamTextDeltaIntent,
|
|
19
20
|
ToolUseIntent,
|
|
20
21
|
} from "./types";
|
|
21
22
|
|
|
@@ -418,6 +419,31 @@ export class AgentPublisher {
|
|
|
418
419
|
return event;
|
|
419
420
|
}
|
|
420
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Publish an ephemeral stream text-delta event.
|
|
424
|
+
* Best-effort only: failures are logged and swallowed to avoid disrupting execution.
|
|
425
|
+
*
|
|
426
|
+
* IMPORTANT: This path intentionally does NOT consume RAL runtime counters.
|
|
427
|
+
* Runtime accounting remains tied to persistent kind:1 publications.
|
|
428
|
+
*/
|
|
429
|
+
async streamTextDelta(intent: StreamTextDeltaIntent, context: EventContext): Promise<void> {
|
|
430
|
+
try {
|
|
431
|
+
const event = this.encoder.encodeStreamTextDelta(intent, context);
|
|
432
|
+
injectTraceContext(event);
|
|
433
|
+
await this.agent.sign(event);
|
|
434
|
+
await event.publish();
|
|
435
|
+
} catch (error) {
|
|
436
|
+
logger.warn("[AgentPublisher.streamTextDelta] Failed to publish stream delta (best-effort)", {
|
|
437
|
+
error: error instanceof Error ? error.message : String(error),
|
|
438
|
+
agent: this.agent.slug,
|
|
439
|
+
conversationId: context.conversationId.substring(0, 12),
|
|
440
|
+
ralNumber: context.ralNumber,
|
|
441
|
+
sequence: intent.sequence,
|
|
442
|
+
deltaLength: intent.delta.length,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
421
447
|
/**
|
|
422
448
|
* Publish a delegation marker event.
|
|
423
449
|
* Delegation markers track the lifecycle of delegation conversations.
|
package/src/nostr/kinds.ts
CHANGED
|
@@ -32,6 +32,7 @@ export const NDKKind = {
|
|
|
32
32
|
TenexConfigUpdate: 25000 as BaseNDKKind, // Encrypted config updates (e.g., APNs device tokens)
|
|
33
33
|
TenexOperationsStatus: 24133 as BaseNDKKind,
|
|
34
34
|
TenexStopCommand: 24134 as BaseNDKKind,
|
|
35
|
+
TenexStreamTextDelta: 24135 as BaseNDKKind,
|
|
35
36
|
} as const;
|
|
36
37
|
|
|
37
38
|
export type NDKKind = (typeof NDKKind)[keyof typeof NDKKind];
|
package/src/nostr/ndkClient.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { logger } from "@/utils/logger";
|
|
|
4
4
|
* TENEX CLI: NDK Singleton
|
|
5
5
|
* Manages a single NDK instance for the CLI
|
|
6
6
|
*/
|
|
7
|
-
import NDK from "@nostr-dev-kit/ndk";
|
|
7
|
+
import NDK, { NDKRelayAuthPolicies } from "@nostr-dev-kit/ndk";
|
|
8
8
|
|
|
9
9
|
let ndk: NDK | undefined;
|
|
10
10
|
|
|
@@ -31,6 +31,9 @@ export async function initNDK(): Promise<void> {
|
|
|
31
31
|
autoConnectUserRelays: true,
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
// Auto-authenticate with relays that require NIP-42 auth
|
|
35
|
+
ndk.relayAuthDefaultPolicy = NDKRelayAuthPolicies.signIn({ ndk });
|
|
36
|
+
|
|
34
37
|
// Connect with timeout - don't block daemon startup if relays are unreachable
|
|
35
38
|
const connectionTimeout = 5000; // 5 seconds
|
|
36
39
|
try {
|
package/src/nostr/types.ts
CHANGED
|
@@ -110,6 +110,17 @@ export interface ToolUseIntent {
|
|
|
110
110
|
usage?: LanguageModelUsageWithCostUsd; // Cumulative usage from previous steps
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Intent for ephemeral stream text-delta events.
|
|
115
|
+
* These events are best-effort live updates and do not replace kind:1 snapshots.
|
|
116
|
+
*/
|
|
117
|
+
export interface StreamTextDeltaIntent {
|
|
118
|
+
/** Delta text payload (coalesced by throttle interval) */
|
|
119
|
+
delta: string;
|
|
120
|
+
/** Strictly monotonic sequence number for client-side reordering */
|
|
121
|
+
sequence: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
113
124
|
/**
|
|
114
125
|
* Intent for intervention review requests.
|
|
115
126
|
* Used when the InterventionService detects that a user hasn't responded
|
|
@@ -160,6 +171,7 @@ export type AgentIntent =
|
|
|
160
171
|
| LessonIntent
|
|
161
172
|
| StatusIntent
|
|
162
173
|
| ToolUseIntent
|
|
174
|
+
| StreamTextDeltaIntent
|
|
163
175
|
| InterventionReviewIntent
|
|
164
176
|
| DelegationMarkerIntent;
|
|
165
177
|
|