mu-core 0.15.0 → 0.16.3

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/src/hooks.ts DELETED
@@ -1,112 +0,0 @@
1
- import type { AgentEndReason, BeforeToolExecResult, LifecycleHooks, TurnResult, UserInputTransform } from './plugin';
2
- import type { ChatMessage, ProviderConfig, ToolCall } from './types/llm';
3
-
4
- export async function runBeforeLlmHooks(
5
- hooks: LifecycleHooks[],
6
- messages: ChatMessage[],
7
- config: ProviderConfig,
8
- ): Promise<ChatMessage[]> {
9
- let current = messages;
10
- for (const hook of hooks) {
11
- if (hook.beforeLlmCall) {
12
- current = await hook.beforeLlmCall(current, config);
13
- }
14
- }
15
- return current;
16
- }
17
-
18
- export async function runAfterLlmHooks(hooks: LifecycleHooks[], result: TurnResult): Promise<TurnResult> {
19
- let current = result;
20
- for (const hook of hooks) {
21
- if (hook.afterLlmCall) {
22
- current = await hook.afterLlmCall(current);
23
- }
24
- }
25
- return current;
26
- }
27
-
28
- /**
29
- * Run every `beforeToolExec` hook in order. Each hook may either return a
30
- * (possibly mutated) `ToolCall` to keep the chain going, or a `ToolBlock` to
31
- * short-circuit execution. Once a hook blocks the call, no further hooks run
32
- * — there's nothing useful to forward.
33
- */
34
- export async function runBeforeToolExecHook(
35
- hooks: LifecycleHooks[],
36
- toolCall: ToolCall,
37
- ): Promise<BeforeToolExecResult> {
38
- let current: BeforeToolExecResult = toolCall;
39
- for (const hook of hooks) {
40
- if (!hook.beforeToolExec) continue;
41
- if ('blocked' in current) return current;
42
- current = await hook.beforeToolExec(current);
43
- }
44
- return current;
45
- }
46
-
47
- export async function runAfterToolExecHook(
48
- hooks: LifecycleHooks[],
49
- toolCall: ToolCall,
50
- result: string,
51
- ): Promise<string> {
52
- let current = result;
53
- for (const hook of hooks) {
54
- if (hook.afterToolExec) {
55
- current = await hook.afterToolExec(toolCall, current);
56
- }
57
- }
58
- return current;
59
- }
60
-
61
- /**
62
- * Pipe a freshly built `ChatMessage` through every `decorateMessage` hook in
63
- * order. Each hook may return a (possibly mutated) message; later hooks see
64
- * the result of the previous one. Used to stamp display hints (agent badge,
65
- * color) without coupling the host to any specific plugin.
66
- */
67
- export async function runDecorateMessageHooks(hooks: LifecycleHooks[], msg: ChatMessage): Promise<ChatMessage> {
68
- let current = msg;
69
- for (const hook of hooks) {
70
- if (hook.decorateMessage) {
71
- current = await hook.decorateMessage(current);
72
- }
73
- }
74
- return current;
75
- }
76
-
77
- export async function runAfterAgentRunHooks(hooks: LifecycleHooks[], reason: AgentEndReason): Promise<void> {
78
- for (const hook of hooks) {
79
- if (hook.afterAgentRun) {
80
- await hook.afterAgentRun(reason);
81
- }
82
- }
83
- }
84
-
85
- /**
86
- * Compose every `transformUserInput` hook. Earlier hooks see the raw text;
87
- * each subsequent hook sees the (possibly rewritten) text emitted by the
88
- * previous one. The first `intercept` or `continue` short-circuits the chain
89
- * — once a plugin has either suppressed the input or appended the user
90
- * message itself, downstream hooks can't safely keep transforming absent
91
- * text, and the host needs to see the terminating signal verbatim so it
92
- * skips its own user-message push (see `useOnSend`).
93
- */
94
- export async function runTransformUserInputHooks(hooks: LifecycleHooks[], text: string): Promise<UserInputTransform> {
95
- let current: UserInputTransform = { kind: 'pass' };
96
- let working = text;
97
- for (const hook of hooks) {
98
- if (!hook.transformUserInput) continue;
99
- const next = await hook.transformUserInput(working);
100
- if (next.kind === 'intercept') {
101
- return next;
102
- }
103
- if (next.kind === 'continue') {
104
- return next;
105
- }
106
- if (next.kind === 'transform') {
107
- working = next.text;
108
- current = next;
109
- }
110
- }
111
- return current;
112
- }
package/src/host/index.ts DELETED
@@ -1,135 +0,0 @@
1
- /**
2
- * `startMu` — generic host bootstrap. Loads a config, builds the plugin
3
- * registry with the new side-channel registries (providers/channels/sessions/
4
- * activity/agents), activates plugins (config first then code-passed), starts
5
- * channels, and returns a handle for shutdown.
6
- *
7
- * Designed for non-coding hosts (Arya etc.). mu-coding currently keeps its
8
- * own bootstrap (Ink TUI lifecycle) and may migrate to this entry point in a
9
- * later iteration.
10
- */
11
-
12
- import type { ActivityBus } from '../activity';
13
- import { createActivityBus } from '../activity';
14
- import type { ChannelRegistry } from '../channel';
15
- import { createChannelRegistry } from '../channel';
16
- import type { Plugin } from '../plugin';
17
- import type { ProviderRegistry } from '../provider/registry';
18
- import { createProviderRegistry } from '../provider/registry';
19
- import { PluginRegistry } from '../registry';
20
- import type { SessionManager } from '../session';
21
- import { createSessionManager } from '../session';
22
- import type { ProviderConfig } from '../types/llm';
23
-
24
- export interface MuConfigShape {
25
- cwd?: string;
26
- baseUrl?: string;
27
- model?: string;
28
- maxTokens?: number;
29
- temperature?: number;
30
- streamTimeoutMs?: number;
31
- systemPrompt?: string;
32
- plugins?: Array<string | { name: string; config?: Record<string, unknown> }>;
33
- }
34
-
35
- export interface StartMuOptions {
36
- configPath?: string;
37
- /** In-memory config — takes precedence over `configPath` when provided. */
38
- config?: MuConfigShape;
39
- /** Plugins passed in code (activated after config-listed plugins). */
40
- plugins?: Plugin[];
41
- /** Cwd default (overrides config.cwd if set). */
42
- cwd?: string;
43
- /**
44
- * Resolves a `config.plugins` entry to a Plugin instance. Hosts (mu-coding,
45
- * Arya) plug their own loader here — typically supports `npm:<name>`
46
- * specifiers, absolute paths, etc. When omitted, config.plugins is ignored.
47
- */
48
- resolvePlugin?: (entry: string | { name: string; config?: Record<string, unknown> }) => Promise<Plugin | null>;
49
- }
50
-
51
- export interface MuHandle {
52
- registry: PluginRegistry;
53
- sessions: SessionManager;
54
- channels: ChannelRegistry;
55
- activity: ActivityBus;
56
- providers: ProviderRegistry;
57
- shutdown: () => Promise<void>;
58
- }
59
-
60
- async function loadConfig(opts: StartMuOptions): Promise<MuConfigShape> {
61
- if (opts.config) return opts.config;
62
- if (!opts.configPath) return {};
63
- const { readFileSync, existsSync } = await import('node:fs');
64
- if (!existsSync(opts.configPath)) return {};
65
- const text = readFileSync(opts.configPath, 'utf8');
66
- return JSON.parse(text) as MuConfigShape;
67
- }
68
-
69
- export async function startMu(options: StartMuOptions = {}): Promise<MuHandle> {
70
- const cfg = await loadConfig(options);
71
- const cwd = options.cwd ?? cfg.cwd ?? process.cwd();
72
-
73
- const providers = createProviderRegistry();
74
- const channels = createChannelRegistry();
75
- const activity = createActivityBus();
76
-
77
- const providerConfig: ProviderConfig = {
78
- baseUrl: cfg.baseUrl ?? 'http://localhost:11434/v1',
79
- model: cfg.model,
80
- maxTokens: cfg.maxTokens ?? 4096,
81
- temperature: cfg.temperature ?? 0.7,
82
- streamTimeoutMs: cfg.streamTimeoutMs ?? 60_000,
83
- systemPrompt: cfg.systemPrompt,
84
- };
85
-
86
- // Build a placeholder for sessions injected after construction (circular:
87
- // SessionManager needs the registry, plugins want to see SessionManager).
88
- let sessions: SessionManager | null = null;
89
- const sessionsProxy: SessionManager = new Proxy({} as SessionManager, {
90
- get(_t, prop) {
91
- if (!sessions) throw new Error('SessionManager not yet initialised');
92
- return (sessions as unknown as Record<string | symbol, unknown>)[prop as string];
93
- },
94
- });
95
-
96
- const registry = new PluginRegistry({
97
- cwd,
98
- config: {},
99
- providers,
100
- channels,
101
- activity,
102
- sessions: sessionsProxy,
103
- });
104
-
105
- sessions = createSessionManager({ registry, config: providerConfig, model: cfg.model ?? 'unknown' });
106
-
107
- // Activate config-listed plugins via the host's resolver. mu-coding wires
108
- // its npm:/path loader; minimal hosts may omit and pass plugins in code.
109
- if (options.resolvePlugin && cfg.plugins) {
110
- for (const entry of cfg.plugins) {
111
- const plugin = await options.resolvePlugin(entry);
112
- if (plugin) await registry.register(plugin);
113
- }
114
- }
115
- // Activate code-passed plugins after config-listed ones (so code overrides
116
- // config-driven hooks compose-wise).
117
- for (const plugin of options.plugins ?? []) {
118
- await registry.register(plugin);
119
- }
120
-
121
- await channels.startAll();
122
-
123
- const sm = sessions;
124
- return {
125
- registry,
126
- sessions: sm,
127
- channels,
128
- activity,
129
- providers,
130
- async shutdown() {
131
- await channels.stopAll();
132
- for (const s of sm.list()) s.abort();
133
- },
134
- };
135
- }
@@ -1,66 +0,0 @@
1
- import { describe, expect, it } from 'bun:test';
2
- import type { Plugin } from '../plugin';
3
- import { startMu } from './index';
4
-
5
- function makePlugin(name: string, marks: string[]): Plugin {
6
- return {
7
- name,
8
- activate() {
9
- marks.push(`activate:${name}`);
10
- },
11
- };
12
- }
13
-
14
- describe('startMu', () => {
15
- it('activates code-passed plugins in order', async () => {
16
- const marks: string[] = [];
17
- const handle = await startMu({
18
- plugins: [makePlugin('a', marks), makePlugin('b', marks)],
19
- });
20
- expect(marks).toEqual(['activate:a', 'activate:b']);
21
- await handle.shutdown();
22
- });
23
-
24
- it('exposes provider/channel/activity/sessions registries', async () => {
25
- const handle = await startMu({});
26
- expect(handle.providers.list()).toEqual([]);
27
- expect(handle.channels.list()).toEqual([]);
28
- expect(handle.sessions.list()).toEqual([]);
29
- expect(typeof handle.activity.emit).toBe('function');
30
- await handle.shutdown();
31
- });
32
-
33
- it('config-listed plugins are activated before code-passed', async () => {
34
- const marks: string[] = [];
35
- const cfgPlugin = makePlugin('cfg', marks);
36
- const codePlugin = makePlugin('code', marks);
37
- const handle = await startMu({
38
- config: { plugins: ['cfg'] },
39
- plugins: [codePlugin],
40
- resolvePlugin: async (entry) => (typeof entry === 'string' && entry === 'cfg' ? cfgPlugin : null),
41
- });
42
- expect(marks).toEqual(['activate:cfg', 'activate:code']);
43
- await handle.shutdown();
44
- });
45
-
46
- it('starts all registered channels', async () => {
47
- const startMarks: string[] = [];
48
- const handle = await startMu({
49
- plugins: [
50
- {
51
- name: 'chan-plugin',
52
- activate(ctx) {
53
- ctx.channels?.register({
54
- id: 'test',
55
- async start() {
56
- startMarks.push('started');
57
- },
58
- });
59
- },
60
- },
61
- ],
62
- });
63
- expect(startMarks).toEqual(['started']);
64
- await handle.shutdown();
65
- });
66
- });
package/src/index.ts DELETED
@@ -1,74 +0,0 @@
1
- export type { ActivityBus, ActivityEvent, ActivityKind, SubAgentEvent, SubAgentEventKind } from './activity';
2
- export { createActivityBus } from './activity';
3
- export { runAgent } from './agent';
4
- export type { Channel, ChannelRegistry, ChannelResponder, InboundKind, InboundMessage, ResponseMode } from './channel';
5
- export { createChannelRegistry } from './channel';
6
- export { runDecorateMessageHooks, runTransformUserInputHooks } from './hooks';
7
- export type { MuConfigShape, MuHandle, StartMuOptions } from './host/index';
8
- export { startMu } from './host/index';
9
- export type {
10
- AgentEndReason,
11
- AgentEvent,
12
- AgentLoopStrategy,
13
- AgentSourceRegistry,
14
- BeforeToolExecResult,
15
- CommandContext,
16
- InputInfoSegment,
17
- LifecycleHooks,
18
- MentionCompletion,
19
- MentionProvider,
20
- MessageBus,
21
- MessageRenderer,
22
- Plugin,
23
- PluginContext,
24
- PluginExtras,
25
- PluginRegistryView,
26
- PluginTool,
27
- PluginToolPermission,
28
- ShortcutHandler,
29
- SlashCommand,
30
- StatusSegment,
31
- ToolBlock,
32
- ToolDisplayHint,
33
- ToolExecutor,
34
- ToolExecutorResult,
35
- ToolResult,
36
- TurnResult,
37
- UserInputTransform,
38
- } from './plugin';
39
- export type {
40
- ChatRequestInput,
41
- ModelsRequestInput,
42
- ParsedChatEvent,
43
- Provider,
44
- ProviderAdapter,
45
- RequestSpec,
46
- } from './provider/adapter';
47
- export { createProvider } from './provider/adapter';
48
- export type { ProviderRegistry } from './provider/registry';
49
- export { createProviderRegistry } from './provider/registry';
50
- export { fetchWithIdleTimeout, readNDJSON, readSSE } from './provider/transport';
51
- export { PluginRegistry, type PluginRegistryOptions } from './registry';
52
- export type {
53
- CreateSessionManagerOptions,
54
- RunTurnOptions,
55
- Session,
56
- SessionEvent,
57
- SessionInit,
58
- SessionManager,
59
- } from './session';
60
- export { createSessionManager } from './session';
61
- export type {
62
- ApiModel,
63
- ChatMessage,
64
- ImageAttachment,
65
- MessageDisplay,
66
- ProviderConfig,
67
- StreamChunk,
68
- StreamOptions,
69
- ToolCall,
70
- ToolDefinition,
71
- ToolResultInfo,
72
- Usage,
73
- } from './types/llm';
74
- export { ConsoleUIService, type UINotifyLevel, type UIService } from './ui';