@namzu/cli 0.0.2 → 0.1.0
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/LICENSE.md +110 -0
- package/dist/bin.js +4 -32
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +124 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.d.ts +2 -0
- package/dist/cli.test.d.ts.map +1 -0
- package/dist/cli.test.js +94 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +10 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/providers.d.ts +17 -0
- package/dist/commands/providers.d.ts.map +1 -0
- package/dist/commands/providers.js +274 -0
- package/dist/commands/providers.js.map +1 -0
- package/dist/commands/registry.d.ts +11 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +28 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/run.d.ts +14 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +73 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/stubs.d.ts +12 -0
- package/dist/commands/stubs.d.ts.map +1 -0
- package/dist/commands/stubs.js +32 -0
- package/dist/commands/stubs.js.map +1 -0
- package/dist/commands/tools.d.ts +16 -0
- package/dist/commands/tools.d.ts.map +1 -0
- package/dist/commands/tools.js +161 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/types.d.ts +25 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/config/load.d.ts +25 -0
- package/dist/config/load.d.ts.map +1 -0
- package/dist/config/load.js +92 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/load.test.d.ts +2 -0
- package/dist/config/load.test.d.ts.map +1 -0
- package/dist/config/load.test.js +57 -0
- package/dist/config/load.test.js.map +1 -0
- package/dist/config/schema.d.ts +29 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +13 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/doctor/registry.d.ts.map +1 -1
- package/dist/doctor/registry.js +3 -1
- package/dist/doctor/registry.js.map +1 -1
- package/dist/doctor/registry.test.js +4 -1
- package/dist/doctor/registry.test.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/clawtool/agents.d.ts +31 -0
- package/dist/integrations/clawtool/agents.d.ts.map +1 -0
- package/dist/integrations/clawtool/agents.js +30 -0
- package/dist/integrations/clawtool/agents.js.map +1 -0
- package/dist/integrations/clawtool/agents.test.d.ts +2 -0
- package/dist/integrations/clawtool/agents.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/agents.test.js +79 -0
- package/dist/integrations/clawtool/agents.test.js.map +1 -0
- package/dist/integrations/clawtool/auth.d.ts +26 -0
- package/dist/integrations/clawtool/auth.d.ts.map +1 -0
- package/dist/integrations/clawtool/auth.js +56 -0
- package/dist/integrations/clawtool/auth.js.map +1 -0
- package/dist/integrations/clawtool/auth.test.d.ts +2 -0
- package/dist/integrations/clawtool/auth.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/auth.test.js +40 -0
- package/dist/integrations/clawtool/auth.test.js.map +1 -0
- package/dist/integrations/clawtool/binary.d.ts +25 -0
- package/dist/integrations/clawtool/binary.d.ts.map +1 -0
- package/dist/integrations/clawtool/binary.js +50 -0
- package/dist/integrations/clawtool/binary.js.map +1 -0
- package/dist/integrations/clawtool/binary.test.d.ts +2 -0
- package/dist/integrations/clawtool/binary.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/binary.test.js +42 -0
- package/dist/integrations/clawtool/binary.test.js.map +1 -0
- package/dist/integrations/clawtool/daemon.d.ts +37 -0
- package/dist/integrations/clawtool/daemon.d.ts.map +1 -0
- package/dist/integrations/clawtool/daemon.js +109 -0
- package/dist/integrations/clawtool/daemon.js.map +1 -0
- package/dist/integrations/clawtool/daemon.test.d.ts +11 -0
- package/dist/integrations/clawtool/daemon.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/daemon.test.js +118 -0
- package/dist/integrations/clawtool/daemon.test.js.map +1 -0
- package/dist/integrations/clawtool/dispatch.d.ts +33 -0
- package/dist/integrations/clawtool/dispatch.d.ts.map +1 -0
- package/dist/integrations/clawtool/dispatch.js +155 -0
- package/dist/integrations/clawtool/dispatch.js.map +1 -0
- package/dist/integrations/clawtool/dispatch.test.d.ts +2 -0
- package/dist/integrations/clawtool/dispatch.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/dispatch.test.js +138 -0
- package/dist/integrations/clawtool/dispatch.test.js.map +1 -0
- package/dist/integrations/clawtool/index.d.ts +11 -0
- package/dist/integrations/clawtool/index.d.ts.map +1 -0
- package/dist/integrations/clawtool/index.js +10 -0
- package/dist/integrations/clawtool/index.js.map +1 -0
- package/dist/integrations/clawtool/mcp.d.ts +51 -0
- package/dist/integrations/clawtool/mcp.d.ts.map +1 -0
- package/dist/integrations/clawtool/mcp.js +156 -0
- package/dist/integrations/clawtool/mcp.js.map +1 -0
- package/dist/integrations/clawtool/mcp.test.d.ts +2 -0
- package/dist/integrations/clawtool/mcp.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/mcp.test.js +126 -0
- package/dist/integrations/clawtool/mcp.test.js.map +1 -0
- package/dist/integrations/clawtool/paths.d.ts +8 -0
- package/dist/integrations/clawtool/paths.d.ts.map +1 -0
- package/dist/integrations/clawtool/paths.js +19 -0
- package/dist/integrations/clawtool/paths.js.map +1 -0
- package/dist/integrations/clawtool/peers.d.ts +67 -0
- package/dist/integrations/clawtool/peers.d.ts.map +1 -0
- package/dist/integrations/clawtool/peers.js +150 -0
- package/dist/integrations/clawtool/peers.js.map +1 -0
- package/dist/integrations/clawtool/plugin.d.ts +42 -0
- package/dist/integrations/clawtool/plugin.d.ts.map +1 -0
- package/dist/integrations/clawtool/plugin.js +38 -0
- package/dist/integrations/clawtool/plugin.js.map +1 -0
- package/dist/integrations/clawtool/state.d.ts +11 -0
- package/dist/integrations/clawtool/state.d.ts.map +1 -0
- package/dist/integrations/clawtool/state.js +41 -0
- package/dist/integrations/clawtool/state.js.map +1 -0
- package/dist/integrations/clawtool/state.test.d.ts +2 -0
- package/dist/integrations/clawtool/state.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/state.test.js +38 -0
- package/dist/integrations/clawtool/state.test.js.map +1 -0
- package/dist/integrations/clawtool/tooling.d.ts +50 -0
- package/dist/integrations/clawtool/tooling.d.ts.map +1 -0
- package/dist/integrations/clawtool/tooling.js +89 -0
- package/dist/integrations/clawtool/tooling.js.map +1 -0
- package/dist/integrations/clawtool/tooling.test.d.ts +2 -0
- package/dist/integrations/clawtool/tooling.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/tooling.test.js +58 -0
- package/dist/integrations/clawtool/tooling.test.js.map +1 -0
- package/dist/integrations/clawtool/types.d.ts +41 -0
- package/dist/integrations/clawtool/types.d.ts.map +1 -0
- package/dist/integrations/clawtool/types.js +9 -0
- package/dist/integrations/clawtool/types.js.map +1 -0
- package/dist/integrations/clipboard/image.d.ts +18 -0
- package/dist/integrations/clipboard/image.d.ts.map +1 -0
- package/dist/integrations/clipboard/image.js +88 -0
- package/dist/integrations/clipboard/image.js.map +1 -0
- package/dist/integrations/providers/discover.d.ts +68 -0
- package/dist/integrations/providers/discover.d.ts.map +1 -0
- package/dist/integrations/providers/discover.js +108 -0
- package/dist/integrations/providers/discover.js.map +1 -0
- package/dist/integrations/providers/discover.test.d.ts +2 -0
- package/dist/integrations/providers/discover.test.d.ts.map +1 -0
- package/dist/integrations/providers/discover.test.js +120 -0
- package/dist/integrations/providers/discover.test.js.map +1 -0
- package/dist/integrations/providers/index.d.ts +10 -0
- package/dist/integrations/providers/index.d.ts.map +1 -0
- package/dist/integrations/providers/index.js +12 -0
- package/dist/integrations/providers/index.js.map +1 -0
- package/dist/integrations/providers/keychain.d.ts +44 -0
- package/dist/integrations/providers/keychain.d.ts.map +1 -0
- package/dist/integrations/providers/keychain.js +154 -0
- package/dist/integrations/providers/keychain.js.map +1 -0
- package/dist/integrations/providers/keychain.test.d.ts +2 -0
- package/dist/integrations/providers/keychain.test.d.ts.map +1 -0
- package/dist/integrations/providers/keychain.test.js +21 -0
- package/dist/integrations/providers/keychain.test.js.map +1 -0
- package/dist/integrations/providers/mask.d.ts +7 -0
- package/dist/integrations/providers/mask.d.ts.map +1 -0
- package/dist/integrations/providers/mask.js +14 -0
- package/dist/integrations/providers/mask.js.map +1 -0
- package/dist/integrations/providers/mask.test.d.ts +2 -0
- package/dist/integrations/providers/mask.test.d.ts.map +1 -0
- package/dist/integrations/providers/mask.test.js +20 -0
- package/dist/integrations/providers/mask.test.js.map +1 -0
- package/dist/integrations/providers/oauth.d.ts +28 -0
- package/dist/integrations/providers/oauth.d.ts.map +1 -0
- package/dist/integrations/providers/oauth.js +65 -0
- package/dist/integrations/providers/oauth.js.map +1 -0
- package/dist/integrations/providers/oauth.test.d.ts +2 -0
- package/dist/integrations/providers/oauth.test.d.ts.map +1 -0
- package/dist/integrations/providers/oauth.test.js +86 -0
- package/dist/integrations/providers/oauth.test.js.map +1 -0
- package/dist/integrations/providers/preferences.d.ts +43 -0
- package/dist/integrations/providers/preferences.d.ts.map +1 -0
- package/dist/integrations/providers/preferences.js +111 -0
- package/dist/integrations/providers/preferences.js.map +1 -0
- package/dist/integrations/providers/preferences.test.d.ts +2 -0
- package/dist/integrations/providers/preferences.test.d.ts.map +1 -0
- package/dist/integrations/providers/preferences.test.js +68 -0
- package/dist/integrations/providers/preferences.test.js.map +1 -0
- package/dist/integrations/providers/registry.d.ts +31 -0
- package/dist/integrations/providers/registry.d.ts.map +1 -0
- package/dist/integrations/providers/registry.js +79 -0
- package/dist/integrations/providers/registry.js.map +1 -0
- package/dist/integrations/providers/schema.d.ts +73 -0
- package/dist/integrations/providers/schema.d.ts.map +1 -0
- package/dist/integrations/providers/schema.js +70 -0
- package/dist/integrations/providers/schema.js.map +1 -0
- package/dist/integrations/providers/schema.test.d.ts +2 -0
- package/dist/integrations/providers/schema.test.d.ts.map +1 -0
- package/dist/integrations/providers/schema.test.js +43 -0
- package/dist/integrations/providers/schema.test.js.map +1 -0
- package/dist/integrations/providers/secrets.d.ts +37 -0
- package/dist/integrations/providers/secrets.d.ts.map +1 -0
- package/dist/integrations/providers/secrets.js +69 -0
- package/dist/integrations/providers/secrets.js.map +1 -0
- package/dist/integrations/providers/store.d.ts +32 -0
- package/dist/integrations/providers/store.d.ts.map +1 -0
- package/dist/integrations/providers/store.js +133 -0
- package/dist/integrations/providers/store.js.map +1 -0
- package/dist/integrations/providers/store.test.d.ts +2 -0
- package/dist/integrations/providers/store.test.d.ts.map +1 -0
- package/dist/integrations/providers/store.test.js +127 -0
- package/dist/integrations/providers/store.test.js.map +1 -0
- package/dist/integrations/sessions/store.d.ts +40 -0
- package/dist/integrations/sessions/store.d.ts.map +1 -0
- package/dist/integrations/sessions/store.js +90 -0
- package/dist/integrations/sessions/store.js.map +1 -0
- package/dist/integrations/subagents/runtime.d.ts +37 -0
- package/dist/integrations/subagents/runtime.d.ts.map +1 -0
- package/dist/integrations/subagents/runtime.js +183 -0
- package/dist/integrations/subagents/runtime.js.map +1 -0
- package/dist/integrations/trust/store.d.ts +16 -0
- package/dist/integrations/trust/store.d.ts.map +1 -0
- package/dist/integrations/trust/store.js +59 -0
- package/dist/integrations/trust/store.js.map +1 -0
- package/dist/integrations/trust/store.test.d.ts +2 -0
- package/dist/integrations/trust/store.test.d.ts.map +1 -0
- package/dist/integrations/trust/store.test.js +50 -0
- package/dist/integrations/trust/store.test.js.map +1 -0
- package/dist/integrations/updates.d.ts +27 -0
- package/dist/integrations/updates.d.ts.map +1 -0
- package/dist/integrations/updates.js +99 -0
- package/dist/integrations/updates.js.map +1 -0
- package/dist/memory/store.d.ts +29 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +74 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/store.test.d.ts +2 -0
- package/dist/memory/store.test.d.ts.map +1 -0
- package/dist/memory/store.test.js +63 -0
- package/dist/memory/store.test.js.map +1 -0
- package/dist/output/formatter.d.ts +30 -0
- package/dist/output/formatter.d.ts.map +1 -0
- package/dist/output/formatter.js +16 -0
- package/dist/output/formatter.js.map +1 -0
- package/dist/output/formatter.test.d.ts +2 -0
- package/dist/output/formatter.test.d.ts.map +1 -0
- package/dist/output/formatter.test.js +88 -0
- package/dist/output/formatter.test.js.map +1 -0
- package/dist/output/index.d.ts +5 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/output/index.js +22 -0
- package/dist/output/index.js.map +1 -0
- package/dist/output/json.d.ts +13 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +51 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/text.d.ts +13 -0
- package/dist/output/text.d.ts.map +1 -0
- package/dist/output/text.js +36 -0
- package/dist/output/text.js.map +1 -0
- package/dist/output/yaml.d.ts +13 -0
- package/dist/output/yaml.d.ts.map +1 -0
- package/dist/output/yaml.js +26 -0
- package/dist/output/yaml.js.map +1 -0
- package/dist/skills/store.d.ts +49 -0
- package/dist/skills/store.d.ts.map +1 -0
- package/dist/skills/store.js +109 -0
- package/dist/skills/store.js.map +1 -0
- package/dist/skills/store.test.d.ts +2 -0
- package/dist/skills/store.test.d.ts.map +1 -0
- package/dist/skills/store.test.js +80 -0
- package/dist/skills/store.test.js.map +1 -0
- package/dist/tui/App.d.ts +18 -0
- package/dist/tui/App.d.ts.map +1 -0
- package/dist/tui/App.js +742 -0
- package/dist/tui/App.js.map +1 -0
- package/dist/tui/Composer.d.ts +17 -0
- package/dist/tui/Composer.d.ts.map +1 -0
- package/dist/tui/Composer.js +123 -0
- package/dist/tui/Composer.js.map +1 -0
- package/dist/tui/LiveActivity.d.ts +22 -0
- package/dist/tui/LiveActivity.d.ts.map +1 -0
- package/dist/tui/LiveActivity.js +46 -0
- package/dist/tui/LiveActivity.js.map +1 -0
- package/dist/tui/Markdown.d.ts +18 -0
- package/dist/tui/Markdown.d.ts.map +1 -0
- package/dist/tui/Markdown.js +68 -0
- package/dist/tui/Markdown.js.map +1 -0
- package/dist/tui/PermissionOverlay.d.ts +14 -0
- package/dist/tui/PermissionOverlay.d.ts.map +1 -0
- package/dist/tui/PermissionOverlay.js +22 -0
- package/dist/tui/PermissionOverlay.js.map +1 -0
- package/dist/tui/Picker.d.ts +20 -0
- package/dist/tui/Picker.d.ts.map +1 -0
- package/dist/tui/Picker.js +72 -0
- package/dist/tui/Picker.js.map +1 -0
- package/dist/tui/ResumePicker.d.ts +12 -0
- package/dist/tui/ResumePicker.d.ts.map +1 -0
- package/dist/tui/ResumePicker.js +26 -0
- package/dist/tui/ResumePicker.js.map +1 -0
- package/dist/tui/StatusBar.d.ts +22 -0
- package/dist/tui/StatusBar.d.ts.map +1 -0
- package/dist/tui/StatusBar.js +73 -0
- package/dist/tui/StatusBar.js.map +1 -0
- package/dist/tui/Transcript.d.ts +29 -0
- package/dist/tui/Transcript.d.ts.map +1 -0
- package/dist/tui/Transcript.js +105 -0
- package/dist/tui/Transcript.js.map +1 -0
- package/dist/tui/TrustPrompt.d.ts +11 -0
- package/dist/tui/TrustPrompt.d.ts.map +1 -0
- package/dist/tui/TrustPrompt.js +13 -0
- package/dist/tui/TrustPrompt.js.map +1 -0
- package/dist/tui/agent.d.ts +163 -0
- package/dist/tui/agent.d.ts.map +1 -0
- package/dist/tui/agent.js +684 -0
- package/dist/tui/agent.js.map +1 -0
- package/dist/tui/agent.test.d.ts +2 -0
- package/dist/tui/agent.test.d.ts.map +1 -0
- package/dist/tui/agent.test.js +225 -0
- package/dist/tui/agent.test.js.map +1 -0
- package/dist/tui/index.d.ts +7 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +29 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/logo.d.ts +23 -0
- package/dist/tui/logo.d.ts.map +1 -0
- package/dist/tui/logo.js +35 -0
- package/dist/tui/logo.js.map +1 -0
- package/dist/tui/markdownParser.d.ts +45 -0
- package/dist/tui/markdownParser.d.ts.map +1 -0
- package/dist/tui/markdownParser.js +127 -0
- package/dist/tui/markdownParser.js.map +1 -0
- package/dist/tui/markdownParser.test.d.ts +2 -0
- package/dist/tui/markdownParser.test.d.ts.map +1 -0
- package/dist/tui/markdownParser.test.js +90 -0
- package/dist/tui/markdownParser.test.js.map +1 -0
- package/dist/tui/mentions.d.ts +22 -0
- package/dist/tui/mentions.d.ts.map +1 -0
- package/dist/tui/mentions.js +59 -0
- package/dist/tui/mentions.js.map +1 -0
- package/dist/tui/mentions.test.d.ts +2 -0
- package/dist/tui/mentions.test.d.ts.map +1 -0
- package/dist/tui/mentions.test.js +35 -0
- package/dist/tui/mentions.test.js.map +1 -0
- package/dist/tui/slashCommands.d.ts +64 -0
- package/dist/tui/slashCommands.d.ts.map +1 -0
- package/dist/tui/slashCommands.js +153 -0
- package/dist/tui/slashCommands.js.map +1 -0
- package/dist/tui/slashCommands.test.d.ts +2 -0
- package/dist/tui/slashCommands.test.d.ts.map +1 -0
- package/dist/tui/slashCommands.test.js +114 -0
- package/dist/tui/slashCommands.test.js.map +1 -0
- package/dist/tui/theme.d.ts +34 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +32 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/tui/types.d.ts +27 -0
- package/dist/tui/types.d.ts.map +1 -0
- package/dist/tui/types.js +7 -0
- package/dist/tui/types.js.map +1 -0
- package/package.json +67 -57
package/dist/tui/App.js
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { writePreferences, } from '../integrations/providers/index.js';
|
|
5
|
+
import { isTrusted, trustDir } from '../integrations/trust/store.js';
|
|
6
|
+
import { appendMemory, composeMemoryPrompt, readMemory } from '../memory/store.js';
|
|
7
|
+
import { composeSkillsPrompt, discoverSkills, loadSkillBody } from '../skills/store.js';
|
|
8
|
+
import { deregisterPeer, drainInbox, heartbeatPeer, listPeers, registerPeer, sendPeerMessage, } from '../integrations/clawtool/peers.js';
|
|
9
|
+
import { checkUpdates } from '../integrations/updates.js';
|
|
10
|
+
import { LiveActivity, formatElapsed } from './LiveActivity.js';
|
|
11
|
+
import { expandFileMentions } from './mentions.js';
|
|
12
|
+
import { Composer } from './Composer.js';
|
|
13
|
+
import { TrustPrompt } from './TrustPrompt.js';
|
|
14
|
+
import { NAMZU_MARK, NAMZU_MARK_COLOR, NAMZU_WORDMARK, NAMZU_WORDMARK_GRADIENT, NAMZU_WORDMARK_MIN_WIDTH, } from './logo.js';
|
|
15
|
+
import { PermissionOverlay } from './PermissionOverlay.js';
|
|
16
|
+
import { Picker } from './Picker.js';
|
|
17
|
+
import { StatusBar } from './StatusBar.js';
|
|
18
|
+
import { Transcript } from './Transcript.js';
|
|
19
|
+
import { createAssistantMessage, createUserMessage } from '@namzu/sdk';
|
|
20
|
+
import { appendMessages, listRecent, loadConversation, openSessions, startConversation, } from '../integrations/sessions/store.js';
|
|
21
|
+
import { createAgentSession, probeAgentSession, } from './agent.js';
|
|
22
|
+
import { ResumePicker } from './ResumePicker.js';
|
|
23
|
+
import { runSlash } from './slashCommands.js';
|
|
24
|
+
import { theme } from './theme.js';
|
|
25
|
+
export function App({ ctx }) {
|
|
26
|
+
const { exit } = useApp();
|
|
27
|
+
const [messages, setMessages] = useState([]);
|
|
28
|
+
const [history, setHistory] = useState([]);
|
|
29
|
+
const [state, setState] = useState('idle');
|
|
30
|
+
const [phase, setPhase] = useState('probing');
|
|
31
|
+
const [session, setSession] = useState(null);
|
|
32
|
+
const [detected, setDetected] = useState([]);
|
|
33
|
+
const [currentProvider, setCurrentProvider] = useState(null);
|
|
34
|
+
const [permission, setPermission] = useState(null);
|
|
35
|
+
const [activeSkills, setActiveSkills] = useState([]);
|
|
36
|
+
const [usage, setUsage] = useState(null);
|
|
37
|
+
// Tools currently executing — rendered live (spinner + elapsed) below the
|
|
38
|
+
// transcript, then committed as static lines on completion.
|
|
39
|
+
const [activeTools, setActiveTools] = useState([]);
|
|
40
|
+
const [expanded, setExpanded] = useState(false);
|
|
41
|
+
// Bumped to reset the <Static> transcript log (on /clear and /resume).
|
|
42
|
+
const [resetKey, setResetKey] = useState(0);
|
|
43
|
+
// Messages typed while a turn is running — auto-sent when it settles.
|
|
44
|
+
const [queued, setQueued] = useState([]);
|
|
45
|
+
const [resumeList, setResumeList] = useState([]);
|
|
46
|
+
const [selectedResume, setSelectedResume] = useState(0);
|
|
47
|
+
const exitArmedRef = useRef(false);
|
|
48
|
+
const abortRef = useRef(null);
|
|
49
|
+
// Source of truth for in-flight tools (the event loop runs across renders, so
|
|
50
|
+
// a ref avoids stale state); `activeTools` mirrors it for rendering.
|
|
51
|
+
const activeToolsRef = useRef([]);
|
|
52
|
+
const clearActiveTools = useCallback(() => {
|
|
53
|
+
activeToolsRef.current = [];
|
|
54
|
+
setActiveTools([]);
|
|
55
|
+
}, []);
|
|
56
|
+
const permissionResolveRef = useRef(null);
|
|
57
|
+
// SDK-backed conversation persistence (DiskSessionStore). `scopeRef` carries
|
|
58
|
+
// the active session id used by query() — mutated in place on /resume so new
|
|
59
|
+
// turns attribute to the resumed conversation.
|
|
60
|
+
const sessionsRef = useRef(null);
|
|
61
|
+
const scopeRef = useRef(null);
|
|
62
|
+
const idRef = useRef(0);
|
|
63
|
+
// This session's clawtool BIAM peer id (null when no clawtool daemon).
|
|
64
|
+
// Registering as a peer lets other terminals/agents see + message us.
|
|
65
|
+
const peerIdRef = useRef(null);
|
|
66
|
+
// Mirror of `state` for callbacks/pollers that need the current value
|
|
67
|
+
// without re-subscribing (the inbox poller decides whether namzu is free).
|
|
68
|
+
const stateRef = useRef(state);
|
|
69
|
+
const nextId = useCallback(() => {
|
|
70
|
+
idRef.current += 1;
|
|
71
|
+
return `m${idRef.current}`;
|
|
72
|
+
}, []);
|
|
73
|
+
// Reset the transcript view: clear the terminal + remount <Static> so its
|
|
74
|
+
// already-printed lines don't linger above fresh content (/clear, /resume).
|
|
75
|
+
const resetTranscript = useCallback(() => {
|
|
76
|
+
if (process.stdout.isTTY)
|
|
77
|
+
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
78
|
+
setResetKey((k) => k + 1);
|
|
79
|
+
}, []);
|
|
80
|
+
const pushMessage = useCallback((role, content, pending = false, glyph, detail, glyphColor, meta) => {
|
|
81
|
+
const id = nextId();
|
|
82
|
+
setMessages((prev) => [
|
|
83
|
+
...prev,
|
|
84
|
+
{ id, role, content, pending, glyph, detail, glyphColor, meta },
|
|
85
|
+
]);
|
|
86
|
+
return id;
|
|
87
|
+
}, [nextId]);
|
|
88
|
+
const appendToMessage = useCallback((id, delta) => {
|
|
89
|
+
setMessages((prev) => prev.map((m) => (m.id === id ? { ...m, content: m.content + delta } : m)));
|
|
90
|
+
}, []);
|
|
91
|
+
const finalizeMessage = useCallback((id, finalContent) => {
|
|
92
|
+
setMessages((prev) => prev.map((m) => m.id === id ? { ...m, content: finalContent ?? m.content, pending: false } : m));
|
|
93
|
+
}, []);
|
|
94
|
+
// Open the SDK session store + start a fresh conversation once. Best-effort:
|
|
95
|
+
// on failure persistence is simply unavailable and the chat still works.
|
|
96
|
+
const ensureSessions = useCallback(async () => {
|
|
97
|
+
if (scopeRef.current)
|
|
98
|
+
return scopeRef.current;
|
|
99
|
+
try {
|
|
100
|
+
const sessions = await openSessions(ctx.cwd);
|
|
101
|
+
const sessionId = await startConversation(sessions);
|
|
102
|
+
sessionsRef.current = sessions;
|
|
103
|
+
scopeRef.current = {
|
|
104
|
+
sessionId,
|
|
105
|
+
threadId: sessions.threadId,
|
|
106
|
+
projectId: sessions.projectId,
|
|
107
|
+
tenantId: sessions.tenantId,
|
|
108
|
+
};
|
|
109
|
+
return scopeRef.current;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}, [ctx.cwd]);
|
|
115
|
+
const hydrateSession = useCallback(async (prefs, detectedNow) => {
|
|
116
|
+
const scope = await ensureSessions();
|
|
117
|
+
const s = await createAgentSession(prefs, detectedNow, scope);
|
|
118
|
+
setSession(s);
|
|
119
|
+
setCurrentProvider(prefs.provider);
|
|
120
|
+
if (s.hasProvider) {
|
|
121
|
+
setPhase('ready');
|
|
122
|
+
pushMessage('system', `Connected to ${s.providerSummary}${s.modelSummary ? ` · ${s.modelSummary}` : ''} · ${s.toolNames.length} tools${s.deferredToolCount > 0 ? ` (+${s.deferredToolCount} on demand)` : ''}`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
setPhase('unhealthy');
|
|
126
|
+
if (s.errorHint)
|
|
127
|
+
pushMessage('system', s.errorHint);
|
|
128
|
+
}
|
|
129
|
+
}, [pushMessage]);
|
|
130
|
+
const runProbe = useCallback(async () => {
|
|
131
|
+
try {
|
|
132
|
+
const probe = await probeAgentSession();
|
|
133
|
+
setDetected(probe.detected);
|
|
134
|
+
if (probe.needsRepickReason) {
|
|
135
|
+
pushMessage('system', probe.needsRepickReason);
|
|
136
|
+
setPhase('picker');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (probe.preferences) {
|
|
140
|
+
await hydrateSession(probe.preferences, probe.detected);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
setPhase('picker');
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
setPhase('unhealthy');
|
|
147
|
+
pushMessage('system', `Failed to probe agents: ${err instanceof Error ? err.message : String(err)}`);
|
|
148
|
+
}
|
|
149
|
+
}, [hydrateSession, pushMessage]);
|
|
150
|
+
// Trust gate runs first: don't touch the folder until the user trusts it.
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (isTrusted(ctx.cwd)) {
|
|
153
|
+
void runProbe();
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
setPhase('trust');
|
|
157
|
+
}
|
|
158
|
+
}, [ctx.cwd, runProbe]);
|
|
159
|
+
const acceptTrust = useCallback(() => {
|
|
160
|
+
try {
|
|
161
|
+
trustDir(ctx.cwd);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Non-fatal: proceed for this session even if persisting failed.
|
|
165
|
+
}
|
|
166
|
+
setPhase('probing');
|
|
167
|
+
void runProbe();
|
|
168
|
+
}, [ctx.cwd, runProbe]);
|
|
169
|
+
const slashCtx = {
|
|
170
|
+
availableTools: session?.toolNames ?? [],
|
|
171
|
+
providerSummary: session?.providerSummary ?? null,
|
|
172
|
+
modelSummary: session?.modelSummary ?? null,
|
|
173
|
+
};
|
|
174
|
+
// `/resume`: open the picker with this folder's recent conversations.
|
|
175
|
+
const doResume = useCallback(async () => {
|
|
176
|
+
const sessions = sessionsRef.current ?? (await ensureSessions(), sessionsRef.current);
|
|
177
|
+
if (!sessions) {
|
|
178
|
+
pushMessage('system', 'Conversation history is unavailable in this folder.');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const recent = await listRecent(sessions);
|
|
183
|
+
// Don't offer the active (empty/just-started) conversation.
|
|
184
|
+
const others = recent.filter((c) => c.id !== scopeRef.current?.sessionId);
|
|
185
|
+
if (others.length === 0) {
|
|
186
|
+
pushMessage('system', 'No past conversations to resume in this folder yet.');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
setResumeList(others);
|
|
190
|
+
setSelectedResume(0);
|
|
191
|
+
setPhase('resume');
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
pushMessage('system', `Could not list conversations: ${err instanceof Error ? err.message : String(err)}`);
|
|
195
|
+
}
|
|
196
|
+
}, [ensureSessions, pushMessage]);
|
|
197
|
+
// `/agents`: list every agent peer clawtool knows about — across terminals
|
|
198
|
+
// and (via clawtool's mDNS) the LAN. clawtool's BIAM registry is the single
|
|
199
|
+
// source of truth; namzu just renders it.
|
|
200
|
+
const doListAgents = useCallback(async () => {
|
|
201
|
+
const peers = await listPeers();
|
|
202
|
+
if (peers.length === 0) {
|
|
203
|
+
pushMessage('system', 'No agent peers found. Is clawtool running? Other namzu / claude / codex / gemini windows register here automatically.');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const lines = peers.map((p) => {
|
|
207
|
+
const mine = p.peer_id === peerIdRef.current ? ' (this one)' : '';
|
|
208
|
+
const where = p.path ? ` · ${p.path}` : '';
|
|
209
|
+
return `● ${p.display_name} [${p.backend}]${where}${mine}`;
|
|
210
|
+
});
|
|
211
|
+
pushMessage('system', `agent peers (${peers.length}):\n${lines.join('\n')}`);
|
|
212
|
+
}, [pushMessage]);
|
|
213
|
+
// Load the chosen conversation into the transcript and continue in it.
|
|
214
|
+
const resumeConversation = useCallback(async (conv) => {
|
|
215
|
+
const sessions = sessionsRef.current;
|
|
216
|
+
const scope = scopeRef.current;
|
|
217
|
+
setPhase('ready');
|
|
218
|
+
if (!sessions || !scope)
|
|
219
|
+
return;
|
|
220
|
+
try {
|
|
221
|
+
const msgs = await loadConversation(sessions, conv.id);
|
|
222
|
+
const restored = msgs
|
|
223
|
+
.filter((m) => m.role === 'user' || m.role === 'assistant')
|
|
224
|
+
.map((m) => ({
|
|
225
|
+
id: nextId(),
|
|
226
|
+
role: m.role,
|
|
227
|
+
content: typeof m.content === 'string' ? m.content : '',
|
|
228
|
+
}));
|
|
229
|
+
resetTranscript();
|
|
230
|
+
setMessages(restored);
|
|
231
|
+
scope.sessionId = conv.id; // new turns now attribute to the resumed session
|
|
232
|
+
pushMessage('system', `Resumed: ${conv.title}`);
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
pushMessage('system', `Could not resume: ${err instanceof Error ? err.message : String(err)}`);
|
|
236
|
+
}
|
|
237
|
+
}, [nextId, pushMessage]);
|
|
238
|
+
// Resolve a pending permission prompt with the user's decision and tear
|
|
239
|
+
// down the overlay. No-op if nothing is pending.
|
|
240
|
+
const resolvePermission = useCallback((decision) => {
|
|
241
|
+
const resolve = permissionResolveRef.current;
|
|
242
|
+
permissionResolveRef.current = null;
|
|
243
|
+
setPermission(null);
|
|
244
|
+
if (resolve)
|
|
245
|
+
resolve(decision);
|
|
246
|
+
}, []);
|
|
247
|
+
// Bridge passed into session.send(): the agent calls this before a
|
|
248
|
+
// non-read-only tool batch; it parks until the user presses y/n/a.
|
|
249
|
+
const onPermission = useCallback((req) => new Promise((resolve) => {
|
|
250
|
+
permissionResolveRef.current = resolve;
|
|
251
|
+
setPermission(req);
|
|
252
|
+
setState('awaiting-permission');
|
|
253
|
+
}), []);
|
|
254
|
+
// Render one agent event onto the transcript. Shared by the local turn loop
|
|
255
|
+
// and the daemon-attach poller, so both paths produce identical output.
|
|
256
|
+
// `st` carries the streaming-assistant bubble id + accumulated text across
|
|
257
|
+
// events within a turn/stream.
|
|
258
|
+
const applyEvent = useCallback((event, st) => {
|
|
259
|
+
const ensureAssistant = () => {
|
|
260
|
+
if (!st.assistantId)
|
|
261
|
+
st.assistantId = pushMessage('assistant', '', true);
|
|
262
|
+
return st.assistantId;
|
|
263
|
+
};
|
|
264
|
+
const closeAssistant = () => {
|
|
265
|
+
if (st.assistantId) {
|
|
266
|
+
finalizeMessage(st.assistantId);
|
|
267
|
+
st.assistantId = null;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
switch (event.kind) {
|
|
271
|
+
case 'delta':
|
|
272
|
+
setState('thinking');
|
|
273
|
+
st.text += event.text;
|
|
274
|
+
appendToMessage(ensureAssistant(), event.text);
|
|
275
|
+
break;
|
|
276
|
+
case 'tool-start': {
|
|
277
|
+
closeAssistant();
|
|
278
|
+
setState('tool');
|
|
279
|
+
const tool = {
|
|
280
|
+
id: event.toolUseId,
|
|
281
|
+
toolName: event.toolName,
|
|
282
|
+
label: formatToolCall(event.toolName, event.summary),
|
|
283
|
+
startedAt: Date.now(),
|
|
284
|
+
detail: event.detail,
|
|
285
|
+
};
|
|
286
|
+
activeToolsRef.current = [...activeToolsRef.current, tool];
|
|
287
|
+
setActiveTools(activeToolsRef.current);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case 'tool-end': {
|
|
291
|
+
const running = activeToolsRef.current;
|
|
292
|
+
// Match strictly by toolUseId. Never fall back to "the first
|
|
293
|
+
// active tool" — under parallel calls that mis-attributes a
|
|
294
|
+
// result to the wrong call. If no id matches, render the
|
|
295
|
+
// completion on its own (label from the end event itself).
|
|
296
|
+
const i = running.findIndex((t) => t.id === event.toolUseId);
|
|
297
|
+
const done = i >= 0 ? running[i] : undefined;
|
|
298
|
+
if (i >= 0) {
|
|
299
|
+
activeToolsRef.current = [...running.slice(0, i), ...running.slice(i + 1)];
|
|
300
|
+
setActiveTools(activeToolsRef.current);
|
|
301
|
+
}
|
|
302
|
+
pushMessage('tool', done?.label ?? formatToolCall(event.toolName, event.summary), false, event.isError ? '✗' : '✓', done?.detail, event.isError ? theme.status.error : theme.status.ok, done ? formatElapsed(Date.now() - done.startedAt) : undefined);
|
|
303
|
+
if (event.isError || event.summary.length > 0 || (event.detail?.length ?? 0) > 0) {
|
|
304
|
+
pushMessage('tool', event.isError ? `failed: ${event.summary}` : event.summary, false, '⎿', event.detail);
|
|
305
|
+
}
|
|
306
|
+
setState(activeToolsRef.current.length > 0 ? 'tool' : 'thinking');
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case 'usage':
|
|
310
|
+
setUsage({ totalTokens: event.totalTokens, costUsd: event.costUsd });
|
|
311
|
+
break;
|
|
312
|
+
case 'task':
|
|
313
|
+
pushMessage('tool', event.subject, false, event.status === 'completed' ? '☑' : '☐');
|
|
314
|
+
break;
|
|
315
|
+
case 'done':
|
|
316
|
+
closeAssistant();
|
|
317
|
+
break;
|
|
318
|
+
case 'error':
|
|
319
|
+
closeAssistant();
|
|
320
|
+
if (event.message !== 'aborted')
|
|
321
|
+
pushMessage('system', `Error: ${event.message}`);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}, [appendToMessage, finalizeMessage, pushMessage]);
|
|
325
|
+
const runTurn = useCallback(async (text, images, replyTo) => {
|
|
326
|
+
if (!session || !session.hasProvider) {
|
|
327
|
+
pushMessage('system', session?.errorHint ?? 'Agent is not ready yet — give it a moment.');
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
// `@path` mentions: the visible message keeps the readable token, but
|
|
331
|
+
// the model receives the file contents inlined.
|
|
332
|
+
const { sendText, attached } = expandFileMentions(text, ctx.cwd);
|
|
333
|
+
const priorForSdk = messages
|
|
334
|
+
.filter((m) => (m.role === 'user' || m.role === 'assistant') && !m.pending)
|
|
335
|
+
.map((m) => ({
|
|
336
|
+
role: m.role,
|
|
337
|
+
content: m.content,
|
|
338
|
+
timestamp: Date.now(),
|
|
339
|
+
}));
|
|
340
|
+
priorForSdk.push({
|
|
341
|
+
role: 'user',
|
|
342
|
+
content: sendText,
|
|
343
|
+
timestamp: Date.now(),
|
|
344
|
+
...(images && images.length > 0 ? { attachments: images } : {}),
|
|
345
|
+
});
|
|
346
|
+
const metaParts = [];
|
|
347
|
+
if (attached.length > 0)
|
|
348
|
+
metaParts.push(`${attached.length} file${attached.length > 1 ? 's' : ''} attached`);
|
|
349
|
+
if (images && images.length > 0)
|
|
350
|
+
metaParts.push(`${images.length} image${images.length > 1 ? 's' : ''}`);
|
|
351
|
+
pushMessage('user', text, false, undefined, undefined, undefined, metaParts.length > 0 ? metaParts.join(' · ') : undefined);
|
|
352
|
+
setState('thinking');
|
|
353
|
+
// The model interleaves text → tool → text across iterations; `applyEvent`
|
|
354
|
+
// (shared with the daemon-attach poller) renders each one in order.
|
|
355
|
+
const st = { assistantId: null, text: '' };
|
|
356
|
+
const ac = new AbortController();
|
|
357
|
+
abortRef.current = ac;
|
|
358
|
+
try {
|
|
359
|
+
for await (const event of session.send(priorForSdk, {
|
|
360
|
+
signal: ac.signal,
|
|
361
|
+
// Bypass mode (--dangerously-skip-permissions / --yolo): omit the
|
|
362
|
+
// permission callback so every tool batch auto-approves.
|
|
363
|
+
onPermission: ctx.skipPermissions ? undefined : onPermission,
|
|
364
|
+
extraSystem: composeSkillsPrompt(activeSkills) ?? undefined,
|
|
365
|
+
})) {
|
|
366
|
+
applyEvent(event, st);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
if (st.assistantId)
|
|
371
|
+
finalizeMessage(st.assistantId);
|
|
372
|
+
pushMessage('system', `Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
373
|
+
}
|
|
374
|
+
finally {
|
|
375
|
+
abortRef.current = null;
|
|
376
|
+
permissionResolveRef.current = null;
|
|
377
|
+
setPermission(null);
|
|
378
|
+
clearActiveTools();
|
|
379
|
+
setState('idle');
|
|
380
|
+
// Persist the turn to the SDK session store (best-effort) so it can
|
|
381
|
+
// be resumed later. User message + the assistant's reply text.
|
|
382
|
+
const sessions = sessionsRef.current;
|
|
383
|
+
const scope = scopeRef.current;
|
|
384
|
+
if (sessions && scope) {
|
|
385
|
+
const turn = [createUserMessage(text)];
|
|
386
|
+
if (st.text.trim().length > 0)
|
|
387
|
+
turn.push(createAssistantMessage(st.text));
|
|
388
|
+
void appendMessages(sessions, scope.sessionId, turn).catch(() => { });
|
|
389
|
+
}
|
|
390
|
+
// If this turn answered an inbound peer message, send the reply back
|
|
391
|
+
// to that peer's inbox — closing the BIAM loop (agent↔agent).
|
|
392
|
+
if (replyTo && st.text.trim().length > 0) {
|
|
393
|
+
void sendPeerMessage(replyTo, st.text.trim(), { from: peerIdRef.current ?? undefined });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}, [activeSkills, applyEvent, ctx.cwd, ctx.skipPermissions, finalizeMessage, messages, onPermission, pushMessage, session]);
|
|
397
|
+
// Send a message to another agent peer's BIAM inbox (cross-terminal /
|
|
398
|
+
// cross-agent). Pick the peer by a substring of its display name or id.
|
|
399
|
+
const doSendPeer = useCallback(async (query, text) => {
|
|
400
|
+
const peers = await listPeers();
|
|
401
|
+
const match = peers.find((p) => p.peer_id !== peerIdRef.current &&
|
|
402
|
+
(p.peer_id === query ||
|
|
403
|
+
p.display_name.toLowerCase().includes(query.toLowerCase()) ||
|
|
404
|
+
p.backend.toLowerCase() === query.toLowerCase()));
|
|
405
|
+
if (!match) {
|
|
406
|
+
pushMessage('system', `No peer matching "${query}". See /agents.`);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const ok = await sendPeerMessage(match.peer_id, text, { from: peerIdRef.current ?? undefined });
|
|
410
|
+
pushMessage('system', ok
|
|
411
|
+
? `Sent to ${match.display_name} [${match.backend}]: ${text}`
|
|
412
|
+
: `Could not reach ${match.display_name}.`);
|
|
413
|
+
}, [pushMessage]);
|
|
414
|
+
const handleSubmit = useCallback((value, images) => {
|
|
415
|
+
setHistory((prev) => [...prev, value]);
|
|
416
|
+
const slash = runSlash(value, slashCtx);
|
|
417
|
+
if (slash) {
|
|
418
|
+
switch (slash.kind) {
|
|
419
|
+
case 'message':
|
|
420
|
+
pushMessage(slash.role, slash.content);
|
|
421
|
+
return;
|
|
422
|
+
case 'clear':
|
|
423
|
+
setMessages([]);
|
|
424
|
+
resetTranscript();
|
|
425
|
+
return;
|
|
426
|
+
case 'exit':
|
|
427
|
+
exit();
|
|
428
|
+
return;
|
|
429
|
+
case 'repick':
|
|
430
|
+
setPhase('picker');
|
|
431
|
+
return;
|
|
432
|
+
case 'remember':
|
|
433
|
+
try {
|
|
434
|
+
appendMemory(slash.text);
|
|
435
|
+
pushMessage('system', `Remembered: ${slash.text}`);
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
pushMessage('system', `Could not save memory: ${err instanceof Error ? err.message : String(err)}`);
|
|
439
|
+
}
|
|
440
|
+
return;
|
|
441
|
+
case 'show-memory': {
|
|
442
|
+
const mem = composeMemoryPrompt(readMemory());
|
|
443
|
+
pushMessage('system', mem ?? 'Nothing remembered yet. Use /remember <text>, or edit ~/.namzu/MEMORY.md.');
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
case 'list-skills': {
|
|
447
|
+
const skills = discoverSkills();
|
|
448
|
+
if (skills.length === 0) {
|
|
449
|
+
pushMessage('system', 'No skills found. Add one at ~/.namzu/skills/<name>/SKILL.md or ./skills/<name>/SKILL.md.');
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const activeNames = new Set(activeSkills.map((s) => s.name));
|
|
453
|
+
const lines = skills.map((s) => `${activeNames.has(s.name) ? '● ' : '○ '}${s.name} — ${s.description}`);
|
|
454
|
+
pushMessage('system', `Skills (● active):\n ${lines.join('\n ')}`);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
case 'load-skill': {
|
|
458
|
+
const info = discoverSkills().find((s) => s.name === slash.name);
|
|
459
|
+
if (!info) {
|
|
460
|
+
pushMessage('system', `No skill named "${slash.name}". See /skills.`);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
const body = loadSkillBody(info);
|
|
465
|
+
setActiveSkills((prev) => [
|
|
466
|
+
...prev.filter((s) => s.name !== info.name),
|
|
467
|
+
{ name: info.name, body },
|
|
468
|
+
]);
|
|
469
|
+
pushMessage('system', `Activated skill: ${info.name}`);
|
|
470
|
+
}
|
|
471
|
+
catch (err) {
|
|
472
|
+
pushMessage('system', `Could not load skill "${slash.name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
case 'resume':
|
|
477
|
+
void doResume();
|
|
478
|
+
return;
|
|
479
|
+
case 'list-agents':
|
|
480
|
+
void doListAgents();
|
|
481
|
+
return;
|
|
482
|
+
case 'send-peer':
|
|
483
|
+
void doSendPeer(slash.peer, slash.text);
|
|
484
|
+
return;
|
|
485
|
+
case 'none':
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// A turn is in flight → queue the message; it auto-sends when idle.
|
|
490
|
+
// (Queued messages are text-only; pasted images aren't carried.)
|
|
491
|
+
if (state !== 'idle') {
|
|
492
|
+
setQueued((q) => [...q, value]);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
void runTurn(value, images);
|
|
496
|
+
}, [activeSkills, doListAgents, doResume, doSendPeer, exit, pushMessage, runTurn, slashCtx, state]);
|
|
497
|
+
// Drain the queue: when a turn settles (idle) and nothing is running,
|
|
498
|
+
// send the next queued message automatically.
|
|
499
|
+
useEffect(() => {
|
|
500
|
+
if (state !== 'idle' || phase !== 'ready' || queued.length === 0 || abortRef.current)
|
|
501
|
+
return;
|
|
502
|
+
const [next, ...rest] = queued;
|
|
503
|
+
setQueued(rest);
|
|
504
|
+
if (next !== undefined)
|
|
505
|
+
void runTurn(next);
|
|
506
|
+
}, [state, phase, queued, runTurn]);
|
|
507
|
+
// BIAM presence (best-effort, no-op without clawtool): register once ready
|
|
508
|
+
// as a peer in clawtool's registry, heartbeat, and deregister on unmount.
|
|
509
|
+
// `registerPeer` ensures clawtool's daemon, so the user never starts it by
|
|
510
|
+
// hand. clawtool also prunes stale peers, so a missed deregister self-heals.
|
|
511
|
+
useEffect(() => {
|
|
512
|
+
if (phase !== 'ready' || peerIdRef.current)
|
|
513
|
+
return;
|
|
514
|
+
const home = process.env.HOME;
|
|
515
|
+
const title = home && ctx.cwd.startsWith(home) ? `~${ctx.cwd.slice(home.length)}` : ctx.cwd;
|
|
516
|
+
let cancelled = false;
|
|
517
|
+
void registerPeer({
|
|
518
|
+
display_name: `namzu ${title}`,
|
|
519
|
+
backend: 'namzu',
|
|
520
|
+
path: ctx.cwd,
|
|
521
|
+
pid: process.pid,
|
|
522
|
+
}).then((id) => {
|
|
523
|
+
if (!cancelled)
|
|
524
|
+
peerIdRef.current = id;
|
|
525
|
+
});
|
|
526
|
+
return () => {
|
|
527
|
+
cancelled = true;
|
|
528
|
+
};
|
|
529
|
+
}, [phase, ctx.cwd]);
|
|
530
|
+
useEffect(() => {
|
|
531
|
+
stateRef.current = state;
|
|
532
|
+
}, [state]);
|
|
533
|
+
// One-shot update check on launch: namzu (npm) + clawtool (`upgrade --check`).
|
|
534
|
+
// Best-effort; surfaces a single notice when something newer is out.
|
|
535
|
+
const updateCheckedRef = useRef(false);
|
|
536
|
+
useEffect(() => {
|
|
537
|
+
if (phase !== 'ready' || updateCheckedRef.current)
|
|
538
|
+
return;
|
|
539
|
+
updateCheckedRef.current = true;
|
|
540
|
+
void checkUpdates(ctx.version).then((ups) => {
|
|
541
|
+
if (ups.length === 0)
|
|
542
|
+
return;
|
|
543
|
+
const lines = ups.map((u) => ` • ${u.name} ${u.current} → ${u.latest} (${u.how})`);
|
|
544
|
+
pushMessage('system', `⬆ Update available:\n${lines.join('\n')}`);
|
|
545
|
+
});
|
|
546
|
+
}, [phase, ctx.version, pushMessage]);
|
|
547
|
+
useEffect(() => {
|
|
548
|
+
const id = peerIdRef.current;
|
|
549
|
+
if (id)
|
|
550
|
+
void heartbeatPeer(id);
|
|
551
|
+
}, [state]);
|
|
552
|
+
// Inbound BIAM: drain our peer inbox so clawtool / another agent can talk
|
|
553
|
+
// TO namzu. Each message is surfaced; when namzu is free it answers and the
|
|
554
|
+
// reply goes back to the sender (closing the agent↔agent loop).
|
|
555
|
+
useEffect(() => {
|
|
556
|
+
if (phase !== 'ready')
|
|
557
|
+
return;
|
|
558
|
+
let stopped = false;
|
|
559
|
+
const tick = async () => {
|
|
560
|
+
const id = peerIdRef.current;
|
|
561
|
+
if (stopped || !id)
|
|
562
|
+
return;
|
|
563
|
+
const msgs = await drainInbox(id);
|
|
564
|
+
let answered = stateRef.current !== 'idle' || Boolean(abortRef.current);
|
|
565
|
+
for (const m of msgs) {
|
|
566
|
+
const text = m.text?.trim();
|
|
567
|
+
if (!text)
|
|
568
|
+
continue;
|
|
569
|
+
pushMessage('system', `📨 ${m.from_peer}: ${text}`);
|
|
570
|
+
// Answer the first message while free; reply routes back via replyTo.
|
|
571
|
+
if (!answered) {
|
|
572
|
+
answered = true;
|
|
573
|
+
void runTurn(text, undefined, m.from_peer);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
const timer = setInterval(() => void tick(), 2000);
|
|
578
|
+
return () => {
|
|
579
|
+
stopped = true;
|
|
580
|
+
clearInterval(timer);
|
|
581
|
+
};
|
|
582
|
+
}, [phase, pushMessage, runTurn]);
|
|
583
|
+
useEffect(() => () => {
|
|
584
|
+
const id = peerIdRef.current;
|
|
585
|
+
if (id)
|
|
586
|
+
void deregisterPeer(id);
|
|
587
|
+
}, []);
|
|
588
|
+
const handlePickerSubmit = useCallback((selection) => {
|
|
589
|
+
const prefs = {
|
|
590
|
+
version: 2,
|
|
591
|
+
provider: selection.provider,
|
|
592
|
+
model: selection.model,
|
|
593
|
+
subagents: { active: [] },
|
|
594
|
+
};
|
|
595
|
+
try {
|
|
596
|
+
writePreferences(prefs);
|
|
597
|
+
}
|
|
598
|
+
catch (err) {
|
|
599
|
+
pushMessage('system', `Could not save preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
void hydrateSession(prefs, detected);
|
|
603
|
+
}, [detected, hydrateSession, pushMessage]);
|
|
604
|
+
const handlePickerCancel = useCallback(() => {
|
|
605
|
+
setPhase('unhealthy');
|
|
606
|
+
pushMessage('system', 'Picker cancelled. Set an LLM credential (ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY / start Ollama) and restart namzu.');
|
|
607
|
+
}, [pushMessage]);
|
|
608
|
+
useInput((input, key) => {
|
|
609
|
+
// Resume picker owns the keyboard while open.
|
|
610
|
+
if (phase === 'resume') {
|
|
611
|
+
if (key.upArrow)
|
|
612
|
+
setSelectedResume((i) => Math.max(0, i - 1));
|
|
613
|
+
else if (key.downArrow)
|
|
614
|
+
setSelectedResume((i) => Math.min(resumeList.length - 1, i + 1));
|
|
615
|
+
else if (key.return) {
|
|
616
|
+
const conv = resumeList[selectedResume];
|
|
617
|
+
if (conv)
|
|
618
|
+
void resumeConversation(conv);
|
|
619
|
+
}
|
|
620
|
+
else if (key.escape || (key.ctrl && input === 'c'))
|
|
621
|
+
setPhase('ready');
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
// Trust gate owns the keyboard until the folder is trusted or we exit.
|
|
625
|
+
if (phase === 'trust') {
|
|
626
|
+
const ch = input.toLowerCase();
|
|
627
|
+
if (ch === 'y' || key.return)
|
|
628
|
+
acceptTrust();
|
|
629
|
+
else if (ch === 'n' || key.escape || (key.ctrl && input === 'c'))
|
|
630
|
+
exit();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
// A pending permission prompt owns the keyboard: y/n/a decide it.
|
|
634
|
+
if (permissionResolveRef.current) {
|
|
635
|
+
const ch = input.toLowerCase();
|
|
636
|
+
if (key.ctrl && (input === 'c' || input === '\x03')) {
|
|
637
|
+
resolvePermission({ kind: 'reject', feedback: 'User interrupted.' });
|
|
638
|
+
abortRef.current?.abort();
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (ch === 'y' || key.return)
|
|
642
|
+
resolvePermission({ kind: 'approve' });
|
|
643
|
+
else if (ch === 'a')
|
|
644
|
+
resolvePermission({ kind: 'approve-all' });
|
|
645
|
+
else if (ch === 'n' || key.escape)
|
|
646
|
+
resolvePermission({ kind: 'reject' });
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
// Esc interrupts a running turn (Ctrl+C stays reserved for exit). Mirrors
|
|
650
|
+
// the Ctrl+C interrupt path: abort, drop the queue, one "Interrupted." line.
|
|
651
|
+
if (key.escape && abortRef.current) {
|
|
652
|
+
abortRef.current.abort();
|
|
653
|
+
abortRef.current = null;
|
|
654
|
+
setQueued([]);
|
|
655
|
+
pushMessage('system', 'Interrupted.');
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
// Ctrl+O toggles expansion of collapsed tool diffs / output.
|
|
659
|
+
if (key.ctrl && input === 'o') {
|
|
660
|
+
setExpanded((e) => !e);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (key.ctrl && (input === 'c' || input === '\x03')) {
|
|
664
|
+
// A turn is running → first Ctrl+C interrupts it, not exits.
|
|
665
|
+
if (abortRef.current) {
|
|
666
|
+
abortRef.current.abort();
|
|
667
|
+
// Drop the ref now so a second Ctrl+C arms exit instead of
|
|
668
|
+
// re-aborting (which spammed "Interrupted." lines), and clear any
|
|
669
|
+
// queued messages — interrupting means stop, not "run the next one".
|
|
670
|
+
abortRef.current = null;
|
|
671
|
+
setQueued([]);
|
|
672
|
+
pushMessage('system', 'Interrupted.');
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (exitArmedRef.current) {
|
|
676
|
+
exit();
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
exitArmedRef.current = true;
|
|
680
|
+
pushMessage('system', 'Press Ctrl+C again to exit.');
|
|
681
|
+
setTimeout(() => {
|
|
682
|
+
exitArmedRef.current = false;
|
|
683
|
+
}, 2000);
|
|
684
|
+
}
|
|
685
|
+
}, { isActive: phase !== 'picker' });
|
|
686
|
+
// Background is left natural — we inherit the terminal's own background
|
|
687
|
+
// (like claude-code / gemini-cli) and only theme the foreground. Forcing
|
|
688
|
+
// a filled bg left mismatched patches around bordered areas, so we don't.
|
|
689
|
+
return (_jsxs(Box, { flexDirection: "column", children: [phase !== 'ready' ? (_jsx(Banner, { version: ctx.version, session: session, bypass: ctx.skipPermissions === true, cwd: ctx.cwd })) : null, _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [phase === 'trust' ? (_jsx(TrustPrompt, { cwd: ctx.cwd })) : phase === 'resume' ? (_jsx(ResumePicker, { conversations: resumeList, selected: selectedResume })) : phase === 'picker' ? (_jsx(Picker, { detected: detected, currentProvider: currentProvider, onSubmit: handlePickerSubmit, onCancel: handlePickerCancel })) : (_jsxs(_Fragment, { children: [_jsx(TranscriptFrame, { children: _jsx(Transcript, { messages: messages.filter((m) => !m.pending), pending: messages.find((m) => m.pending) ?? null, state: state, expanded: expanded, resetKey: resetKey, header: phase === 'ready' ? (_jsx(Banner, { version: ctx.version, session: session, bypass: ctx.skipPermissions === true, cwd: ctx.cwd })) : undefined }) }), _jsx(LiveActivity, { activeTools: activeTools, thinking: state === 'thinking' && !messages.some((m) => m.pending) }), permission ? (_jsx(PermissionOverlay, { toolCalls: permission.toolCalls })) : (_jsxs(ComposerFrame, { focus: state === 'idle' && phase === 'ready', children: [queued.length > 0 ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: theme.text.muted, children: ["\u23CE ", queued.length, " message", queued.length > 1 ? 's' : '', " queued \u2014 sending when ready"] }) })) : null, _jsx(Composer, { disabled: phase !== 'ready' || state === 'awaiting-permission', onSubmit: handleSubmit, history: history })] }))] })), _jsx(Box, { paddingTop: 1, children: _jsx(StatusBar, { cwd: ctx.cwd, provider: session?.providerSummary ?? null, model: session?.modelSummary ?? null, state: state, hint: hintForPhase(phase, state), usage: usage, contextWindow: contextWindowFor(session?.modelSummary ?? null) }) })] })] }));
|
|
690
|
+
}
|
|
691
|
+
function Banner({ version, session, bypass, cwd, }) {
|
|
692
|
+
const cols = process.stdout.columns ?? 80;
|
|
693
|
+
const wide = cols >= NAMZU_WORDMARK_MIN_WIDTH;
|
|
694
|
+
const provider = session?.providerSummary;
|
|
695
|
+
const model = session?.modelSummary;
|
|
696
|
+
const home = process.env.HOME;
|
|
697
|
+
const prettyCwd = home && cwd.startsWith(home) ? `~${cwd.slice(home.length)}` : cwd;
|
|
698
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingTop: 1, paddingBottom: 1, children: [_jsxs(Box, { flexDirection: "row", children: [wide ? (_jsx(Box, { flexDirection: "column", marginRight: 2, children: NAMZU_WORDMARK.map((line, i) => (_jsx(Text, { color: NAMZU_WORDMARK_GRADIENT[i], bold: true, children: line }, `wm-${i}`))) })) : (_jsxs(Text, { color: NAMZU_MARK_COLOR, children: [NAMZU_MARK, " "] })), _jsxs(Box, { flexDirection: "column", marginTop: wide ? 1 : 0, children: [_jsxs(Text, { children: [_jsx(Text, { color: theme.text.secondary, children: "Cogitave" }), wide ? null : (_jsxs(Text, { color: NAMZU_MARK_COLOR, bold: true, children: [' ', "Namzu"] })), _jsxs(Text, { color: theme.text.muted, children: [" v", version] })] }), _jsx(Text, { color: theme.text.secondary, children: provider ? `${provider}${model ? ` · ${model}` : ''}` : 'the agent in your terminal' }), _jsx(Text, { color: theme.text.muted, children: prettyCwd })] })] }), bypass ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.status.error, bold: true, children: "\u26A0 bypass permissions \u2014 tools run without asking" }) })) : null] }));
|
|
699
|
+
}
|
|
700
|
+
function TranscriptFrame({ children }) {
|
|
701
|
+
// Borderless, edge-to-edge — the message glyph gutter provides structure.
|
|
702
|
+
return _jsx(Box, { flexDirection: "column", children: children });
|
|
703
|
+
}
|
|
704
|
+
/** Approximate context window (tokens) per model, for the status gauge. The
|
|
705
|
+
* `[1m]` long-context variants get 1M; everything else defaults to 200k. */
|
|
706
|
+
function contextWindowFor(model) {
|
|
707
|
+
if (!model)
|
|
708
|
+
return 200_000;
|
|
709
|
+
if (model.includes('[1m]') || model.includes('-1m'))
|
|
710
|
+
return 1_000_000;
|
|
711
|
+
return 200_000;
|
|
712
|
+
}
|
|
713
|
+
function ComposerFrame({ focus, children, }) {
|
|
714
|
+
// Input-field look: a rounded rule above and below the composer, no side
|
|
715
|
+
// borders, so the input reads as a field rather than a heavy box.
|
|
716
|
+
return (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderTop: true, borderBottom: true, borderLeft: false, borderRight: false, borderColor: focus ? theme.border.focus : theme.border.default, marginTop: 1, children: children }));
|
|
717
|
+
}
|
|
718
|
+
// Claude-Code-style tool call label: `Bash(ls -la)`, `Read(file.ts)`.
|
|
719
|
+
// The `clawtool_` prefix is stripped and the name title-cased.
|
|
720
|
+
function formatToolCall(toolName, summary) {
|
|
721
|
+
const base = toolName.replace(/^clawtool_/, '');
|
|
722
|
+
const display = base.length > 0 ? base[0]?.toUpperCase() + base.slice(1) : base;
|
|
723
|
+
return summary.length > 0 ? `${display}(${summary})` : display;
|
|
724
|
+
}
|
|
725
|
+
function hintForPhase(phase, state) {
|
|
726
|
+
if (phase === 'trust')
|
|
727
|
+
return 'y trust this folder · n exit';
|
|
728
|
+
if (phase === 'resume')
|
|
729
|
+
return '↑↓ navigate · enter resume · esc cancel';
|
|
730
|
+
if (phase === 'probing')
|
|
731
|
+
return 'discovering providers…';
|
|
732
|
+
if (phase === 'picker')
|
|
733
|
+
return '↑↓ navigate · enter accept · esc cancel';
|
|
734
|
+
if (phase === 'unhealthy')
|
|
735
|
+
return 'Ctrl+C ×2 to exit';
|
|
736
|
+
if (state === 'awaiting-permission')
|
|
737
|
+
return 'y approve · n reject · a approve all';
|
|
738
|
+
if (state !== 'idle')
|
|
739
|
+
return 'agent is working — esc to interrupt';
|
|
740
|
+
return '/help · @file / Ctrl+V to attach · Ctrl+C ×2 to exit';
|
|
741
|
+
}
|
|
742
|
+
//# sourceMappingURL=App.js.map
|