@agentprojectcontext/apx 1.32.0 → 1.32.2
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/package.json +6 -1
- package/skills/apc-context/SKILL.md +5 -2
- package/skills/apx/SKILL.md +3 -3
- package/skills/apx-agency-agents/SKILL.md +5 -5
- package/skills/apx-agent/SKILL.md +7 -7
- package/skills/apx-mcp/SKILL.md +6 -4
- package/skills/apx-mcp-builder/SKILL.md +4 -7
- package/skills/apx-project/SKILL.md +4 -5
- package/skills/apx-routine/SKILL.md +14 -12
- package/skills/apx-runtime/SKILL.md +5 -3
- package/skills/apx-sessions/SKILL.md +5 -5
- package/skills/apx-skill-builder/SKILL.md +10 -6
- package/skills/apx-task/SKILL.md +8 -8
- package/skills/apx-telegram/SKILL.md +23 -7
- package/skills/apx-voice/SKILL.md +8 -6
- package/src/core/{agent-system.js → agent/build-agent-system.js} +10 -12
- package/src/core/agent/index.js +0 -2
- package/src/core/{agent-memory.js → agent/memory.js} +2 -2
- package/src/core/agent/model-router.js +21 -43
- package/src/core/agent/prompt-builder.js +17 -63
- package/src/core/agent/prompts/action-discipline.md +17 -0
- package/src/core/agent/prompts/channels/code.md +8 -12
- package/src/core/agent/prompts/channels/desktop.md +6 -4
- package/src/core/agent/prompts/channels/routine.md +10 -1
- package/src/core/agent/prompts/channels/telegram.md +5 -0
- package/src/core/agent/prompts/channels/web_code.md +20 -0
- package/src/core/agent/prompts/modes/voice.md +2 -2
- package/src/core/agent/prompts/super-agent-base.md +2 -2
- package/src/core/agent/run-agent.js +37 -35
- package/src/core/agent/runtime-bridge.js +42 -0
- package/src/core/agent/self-memory.js +19 -9
- package/src/core/agent/skills/catalog.js +65 -0
- package/src/core/agent/skills/index.js +6 -0
- package/src/{host/daemon/skills-loader.js → core/agent/skills/loader.js} +3 -3
- package/src/core/agent/skills/rag.js +91 -0
- package/src/core/agent/skills/trigger.js +71 -0
- package/src/{host/daemon → core/agent}/super-agent.js +5 -5
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/add-project.js +3 -4
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-agent.js +2 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-mcp.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-runtime.js +10 -11
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/create-task.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/discover-tools.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/edit-file.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/import-agent.js +4 -5
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-agents.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-skills.js +7 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-tasks.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-vault-agents.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/load-skill.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-agent-memory.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-self-memory.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/remember.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/run-shell.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-messages.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-sessions.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/send-telegram.js +0 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-identity.js +1 -3
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-permission-mode.js +1 -3
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/tail-messages.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/transcribe-audio.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/write-file.js +1 -2
- package/src/core/agent/tools/helpers.js +74 -0
- package/src/{host/daemon/super-agent-tools → core/agent/tools}/registry-bridge.js +3 -3
- package/src/{host/daemon/super-agent-tools/index.js → core/agent/tools/registry.js} +31 -32
- package/src/core/apc/agents-vault.js +37 -0
- package/src/core/{scaffold.js → apc/scaffold.js} +4 -5
- package/src/core/{config.js → config/index.js} +21 -27
- package/src/core/config/paths.js +32 -0
- package/src/core/constants/actors.js +8 -0
- package/src/core/constants/channels.js +19 -0
- package/src/core/constants/index.js +5 -0
- package/src/core/constants/permissions.js +17 -0
- package/src/core/constants/roles.js +9 -0
- package/src/core/engines/_streaming.js +63 -0
- package/src/core/engines/anthropic.js +11 -22
- package/src/core/engines/ollama.js +7 -16
- package/src/core/identity/index.js +8 -0
- package/src/core/{identity.js → identity/self.js} +5 -5
- package/src/core/{telegram-identity.js → identity/telegram.js} +1 -1
- package/src/core/logging.js +1 -1
- package/src/core/mascot.js +1 -1
- package/src/core/memory/active-threads.js +10 -10
- package/src/core/memory/broker.js +9 -9
- package/src/core/memory/compactor.js +2 -2
- package/src/core/memory/index.js +2 -2
- package/src/core/memory/indexer.js +1 -1
- package/src/core/{code-sessions-store.js → stores/code-sessions.js} +3 -7
- package/src/core/{messages-store.js → stores/messages.js} +6 -4
- package/src/core/stores/routine-memory.js +71 -0
- package/src/core/{routines-store.js → stores/routines.js} +1 -3
- package/src/core/stores/runtime-sessions.js +99 -0
- package/src/core/{tasks-store.js → stores/tasks.js} +3 -8
- package/src/core/update-check.js +1 -1
- package/src/core/util/ids.js +14 -0
- package/src/core/util/index.js +2 -0
- package/src/core/util/text-similarity.js +52 -0
- package/src/core/util/time.js +9 -0
- package/src/core/voice/tts.js +1 -1
- package/src/host/daemon/api/admin-config.js +4 -3
- package/src/host/daemon/api/admin.js +1 -1
- package/src/host/daemon/api/agents.js +4 -25
- package/src/host/daemon/api/artifacts.js +1 -1
- package/src/host/daemon/api/code.js +48 -16
- package/src/host/daemon/api/confirm.js +1 -1
- package/src/host/daemon/api/connections.js +2 -2
- package/src/host/daemon/api/conversations.js +2 -2
- package/src/host/daemon/api/deck.js +1 -1
- package/src/host/daemon/api/desktop.js +1 -1
- package/src/host/daemon/api/embeddings.js +4 -4
- package/src/host/daemon/api/engines.js +2 -2
- package/src/host/daemon/api/exec.js +3 -3
- package/src/host/daemon/api/identity.js +1 -1
- package/src/host/daemon/api/mcps.js +1 -1
- package/src/host/daemon/api/messages.js +1 -1
- package/src/host/daemon/api/runtimes.js +9 -8
- package/src/host/daemon/api/sessions-search.js +1 -1
- package/src/host/daemon/api/sessions.js +2 -2
- package/src/host/daemon/api/shared.js +5 -4
- package/src/host/daemon/api/skills.js +30 -0
- package/src/host/daemon/api/super-agent.js +29 -9
- package/src/host/daemon/api/tasks.js +2 -2
- package/src/host/daemon/api/telegram.js +1 -1
- package/src/host/daemon/api/tools.js +6 -6
- package/src/host/daemon/api/tts.js +2 -2
- package/src/host/daemon/api/voice.js +14 -12
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/compact.js +1 -1
- package/src/host/daemon/db.js +4 -4
- package/src/host/daemon/desktop-ws.js +1 -1
- package/src/host/daemon/index.js +4 -4
- package/src/host/daemon/plugins/{desktop.js → desktop/index.js} +11 -6
- package/src/host/daemon/plugins/index.js +2 -2
- package/src/host/daemon/plugins/{telegram.js → telegram/index.js} +66 -195
- package/src/host/daemon/plugins/telegram/media.js +162 -0
- package/src/host/daemon/projects-helpers.js +54 -0
- package/src/host/daemon/routines.js +28 -12
- package/src/host/daemon/smoke.js +2 -2
- package/src/host/daemon/token-store.js +1 -1
- package/src/host/daemon/transcription.js +2 -2
- package/src/host/daemon/wakeup.js +2 -2
- package/src/interfaces/cli/commands/agent.js +3 -3
- package/src/interfaces/cli/commands/command.js +1 -1
- package/src/interfaces/cli/commands/config.js +3 -2
- package/src/interfaces/cli/commands/desktop.js +1 -1
- package/src/interfaces/cli/commands/exec.js +2 -1
- package/src/interfaces/cli/commands/identity.js +2 -2
- package/src/interfaces/cli/commands/init.js +1 -1
- package/src/interfaces/cli/commands/mcp.js +1 -1
- package/src/interfaces/cli/commands/memory.js +2 -2
- package/src/interfaces/cli/commands/model.js +16 -6
- package/src/interfaces/cli/commands/project.js +1 -1
- package/src/interfaces/cli/commands/routine.js +58 -0
- package/src/interfaces/cli/commands/search.js +1 -1
- package/src/interfaces/cli/commands/session.js +4 -4
- package/src/interfaces/cli/commands/setup.js +4 -3
- package/src/interfaces/cli/commands/skills.js +25 -4
- package/src/interfaces/cli/commands/status.js +1 -1
- package/src/interfaces/cli/commands/sys.js +11 -4
- package/src/interfaces/cli/commands/update.js +1 -1
- package/src/interfaces/cli/index.js +4 -4
- package/src/interfaces/cli/postinstall.js +2 -2
- package/src/interfaces/mcp-server/index.js +1 -1
- package/src/interfaces/tui/component/prompt/index.tsx +3 -1
- package/src/interfaces/tui/context/sdk-apx.tsx +47 -7
- package/src/interfaces/tui/context/sync-apx.tsx +20 -2
- package/src/interfaces/tui/context/sync.tsx +2 -1
- package/src/interfaces/tui/routes/session/index.tsx +151 -136
- package/src/interfaces/tui/routes/session/sidebar-apx.tsx +37 -15
- package/src/interfaces/tui/run.ts +2 -0
- package/src/interfaces/web/dist/assets/index-34U_Mp1M.css +1 -0
- package/src/interfaces/web/dist/assets/index-BkybwwRn.js +570 -0
- package/src/interfaces/web/dist/assets/index-BkybwwRn.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +3 -3
- package/src/interfaces/web/src/App.tsx +51 -32
- package/src/interfaces/web/src/components/RobyBubble.tsx +12 -6
- package/src/interfaces/web/src/components/UiSelect.tsx +1 -1
- package/src/interfaces/web/src/components/chat/SkillPicker.tsx +77 -0
- package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +33 -18
- package/src/interfaces/web/src/components/common/TabLayout.tsx +9 -5
- package/src/interfaces/web/src/components/common/TabNav.tsx +3 -3
- package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +4 -2
- package/src/interfaces/web/src/hooks/useChat.ts +47 -2
- package/src/interfaces/web/src/hooks/useNavCollapseCtx.tsx +59 -0
- package/src/interfaces/web/src/hooks/usePersonaName.ts +11 -0
- package/src/interfaces/web/src/i18n/en.ts +7 -7
- package/src/interfaces/web/src/i18n/es.ts +7 -7
- package/src/interfaces/web/src/lib/api/skills.ts +25 -0
- package/src/interfaces/web/src/lib/api.ts +1 -0
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +18 -18
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +5 -18
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -8
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +39 -40
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +12 -9
- package/src/skills/apc-context/SKILL.md +159 -0
- package/src/core/agent/ghost-guard.js +0 -24
- package/src/core/agent/prompts/channels/terminal.md +0 -16
- package/src/host/daemon/apc-runtime-context.js +0 -124
- package/src/host/daemon/super-agent-tools/helpers.js +0 -124
- package/src/host/daemon/tool-call-parser.js +0 -2
- package/src/interfaces/web/dist/assets/index-63P_ji1a.js +0 -571
- package/src/interfaces/web/dist/assets/index-63P_ji1a.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-DLWy6dYz.css +0 -1
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/ask-questions.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-files.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-mcps.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-projects.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-file.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-files.js +0 -0
- /package/src/core/agent/{pseudo-tools.js → tools/pseudo-tools.js} +0 -0
- /package/src/core/agent/{tool-call-parser.js → tools/tool-call-parser.js} +0 -0
- /package/src/core/{parser.js → apc/parser.js} +0 -0
- /package/src/core/{apc-skill-sync.js → apc/skill-sync.js} +0 -0
- /package/src/core/{artifacts-store.js → stores/artifacts.js} +0 -0
- /package/src/{host/daemon → core/stores}/engine-sessions.js +0 -0
- /package/src/core/{session-store.js → stores/sessions.js} +0 -0
- /package/src/host/daemon/plugins/{telegram-ask.js → telegram/ask.js} +0 -0
|
@@ -18,7 +18,7 @@ import { fileURLToPath } from "node:url";
|
|
|
18
18
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19
19
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20
20
|
import { z } from "zod";
|
|
21
|
-
import { findApfRoot } from "
|
|
21
|
+
import { findApfRoot } from "#core/apc/parser.js";
|
|
22
22
|
import { ensureDaemon, http } from "../../cli/http.js";
|
|
23
23
|
|
|
24
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -1569,7 +1569,9 @@ export function Prompt(props: PromptProps) {
|
|
|
1569
1569
|
{(agent) => (
|
|
1570
1570
|
<>
|
|
1571
1571
|
<text fg={fadeColor(highlight(), agentMetaAlpha())}>
|
|
1572
|
-
{
|
|
1572
|
+
{/* APX: show the coding mode (Build) here, not the agent —
|
|
1573
|
+
the agent (Roby) is shown in the sidebar. */}
|
|
1574
|
+
{store.mode === "shell" ? "Shell" : "Build"}
|
|
1573
1575
|
</text>
|
|
1574
1576
|
<Show when={store.mode === "normal"}>
|
|
1575
1577
|
<box flexDirection="row" gap={1}>
|
|
@@ -4,10 +4,23 @@ import { onCleanup } from "solid-js"
|
|
|
4
4
|
import fs from "node:fs"
|
|
5
5
|
import os from "node:os"
|
|
6
6
|
import path from "node:path"
|
|
7
|
-
import { spawn } from "node:child_process"
|
|
7
|
+
import { spawn, execSync } from "node:child_process"
|
|
8
8
|
|
|
9
9
|
const TOKEN_PATH = path.join(os.homedir(), ".apx", "daemon.token")
|
|
10
10
|
|
|
11
|
+
/** Current git branch for `dir`, or "" when not a repo. Cheap, best-effort. */
|
|
12
|
+
function gitBranch(dir: string): string {
|
|
13
|
+
try {
|
|
14
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
15
|
+
cwd: dir,
|
|
16
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
17
|
+
encoding: "utf8",
|
|
18
|
+
}).trim()
|
|
19
|
+
} catch {
|
|
20
|
+
return ""
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
function readToken(): string {
|
|
12
25
|
try {
|
|
13
26
|
return fs.readFileSync(TOKEN_PATH, "utf8").trim()
|
|
@@ -74,10 +87,21 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|
|
74
87
|
prompt: string,
|
|
75
88
|
previousMessages: Array<{ role: string; content: string }> = [],
|
|
76
89
|
) {
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
// the
|
|
80
|
-
|
|
90
|
+
// Run on the `code` channel and hand the daemon our working directory so
|
|
91
|
+
// the agent knows WHERE it is (CWD/project) — otherwise it falls back to
|
|
92
|
+
// the generic API channel with no cwd and asks "which file? which project?".
|
|
93
|
+
// maxIters gives room to chain read→edit→verify; the code.md prompt already
|
|
94
|
+
// carries the "keep going until done" guidance. We deliberately do NOT send
|
|
95
|
+
// completionContract here — on weaker models (e.g. gemini-flash) the hard
|
|
96
|
+
// loop-until-finish contract causes runaway edit/rewrite loops.
|
|
97
|
+
const body: Record<string, unknown> = {
|
|
98
|
+
prompt,
|
|
99
|
+
previousMessages,
|
|
100
|
+
channel: "code",
|
|
101
|
+
channelMeta: { cwd: props.directory ?? process.cwd() },
|
|
102
|
+
maxIters: 40,
|
|
103
|
+
maxTokens: 8192,
|
|
104
|
+
}
|
|
81
105
|
if (currentModel) body.model = currentModel
|
|
82
106
|
const res = await fetch(`${props.url}/projects/${props.pid}/super-agent/chat/stream`, {
|
|
83
107
|
method: "POST",
|
|
@@ -101,7 +125,12 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|
|
101
125
|
const reader = res.body.getReader()
|
|
102
126
|
const dec = new TextDecoder()
|
|
103
127
|
let buf = ""
|
|
104
|
-
|
|
128
|
+
// The daemon may keep the HTTP connection open after the final event, so
|
|
129
|
+
// we can't rely on stream-close to know the turn is done. Resolve as soon
|
|
130
|
+
// as we see `final` or `error` — otherwise the caller's `await` hangs and
|
|
131
|
+
// the next message queues forever.
|
|
132
|
+
let finished = false
|
|
133
|
+
while (!finished) {
|
|
105
134
|
const { done, value } = await reader.read()
|
|
106
135
|
if (done) break
|
|
107
136
|
buf += dec.decode(value, { stream: true })
|
|
@@ -158,16 +187,25 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|
|
158
187
|
usage: ev.result?.usage,
|
|
159
188
|
name: ev.result?.name,
|
|
160
189
|
})
|
|
190
|
+
finished = true
|
|
161
191
|
break
|
|
162
192
|
case "error":
|
|
163
193
|
emitter.emit("event", { type: "error", sessionID, error: ev.error })
|
|
194
|
+
finished = true
|
|
164
195
|
break
|
|
165
196
|
}
|
|
166
197
|
} catch {
|
|
167
198
|
// ignore parse errors for partial lines
|
|
168
199
|
}
|
|
200
|
+
if (finished) break
|
|
169
201
|
}
|
|
170
202
|
}
|
|
203
|
+
// Stop reading and release the connection so the awaiting caller resumes.
|
|
204
|
+
try {
|
|
205
|
+
await reader.cancel()
|
|
206
|
+
} catch {
|
|
207
|
+
/* already closed */
|
|
208
|
+
}
|
|
171
209
|
}
|
|
172
210
|
|
|
173
211
|
// The APX daemon has no generic "create session" route — a chat turn is
|
|
@@ -296,8 +334,10 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|
|
296
334
|
// passed, show "APX" in the sidebar.
|
|
297
335
|
agent: props.agent ?? "APX",
|
|
298
336
|
model: props.model ?? "claude-3-5-sonnet",
|
|
299
|
-
//
|
|
337
|
+
// Working directory (the user's project root, passed via --cwd) and its
|
|
338
|
+
// current git branch — shown in the sidebar / footer like OpenCode.
|
|
300
339
|
directory: props.directory ?? process.cwd(),
|
|
340
|
+
branch: gitBranch(props.directory ?? process.cwd()),
|
|
301
341
|
event: emitter,
|
|
302
342
|
client,
|
|
303
343
|
streamChat,
|
|
@@ -45,6 +45,10 @@ export interface ApxMessage {
|
|
|
45
45
|
/** shell: process exit code (undefined while running) */
|
|
46
46
|
exitCode?: number | null
|
|
47
47
|
createdAt: number
|
|
48
|
+
/** assistant: model that produced the turn (from model_start). */
|
|
49
|
+
model?: string
|
|
50
|
+
/** assistant: when the final answer landed (for response-time display). */
|
|
51
|
+
completedAt?: number
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
export interface ApxUsage {
|
|
@@ -133,9 +137,11 @@ export const { use: useApxSync, provider: ApxSyncProvider } = createSimpleContex
|
|
|
133
137
|
pushUser(event.sessionID, event.text)
|
|
134
138
|
break
|
|
135
139
|
|
|
136
|
-
case "model_start":
|
|
137
|
-
ensureAssistant(event.sessionID)
|
|
140
|
+
case "model_start": {
|
|
141
|
+
const id = ensureAssistant(event.sessionID)
|
|
142
|
+
if (event.model) patchAssistant(event.sessionID, id, (msg) => { if (!msg.model) msg.model = event.model })
|
|
138
143
|
break
|
|
144
|
+
}
|
|
139
145
|
|
|
140
146
|
case "assistant_text": {
|
|
141
147
|
if (!event.text) break
|
|
@@ -205,6 +211,7 @@ export const { use: useApxSync, provider: ApxSyncProvider } = createSimpleContex
|
|
|
205
211
|
const id = activeAssistant.id
|
|
206
212
|
patchAssistant(sessionID, id, (msg) => {
|
|
207
213
|
msg.streaming = false
|
|
214
|
+
msg.completedAt = Date.now()
|
|
208
215
|
const parts = msg.parts ?? (msg.parts = [])
|
|
209
216
|
const finalText = (event.text ?? "").trim()
|
|
210
217
|
const lastText = [...parts].reverse().find((p) => p.kind === "text") as
|
|
@@ -376,6 +383,17 @@ export const { use: useApxSync, provider: ApxSyncProvider } = createSimpleContex
|
|
|
376
383
|
}
|
|
377
384
|
|
|
378
385
|
return {
|
|
386
|
+
// The opencode sync shim (context/sync.tsx) reads these. apx has no async
|
|
387
|
+
// bootstrap / message-fetch, so we're always "ready". `data.messages` is
|
|
388
|
+
// only consumed by the Prompt's cost footer, which expects opencode's
|
|
389
|
+
// Message shape (item.tokens, providerID, …) that ApxMessage doesn't have.
|
|
390
|
+
// We render our own message list from session.messages(), so an empty map
|
|
391
|
+
// here is correct and avoids the Prompt crashing on missing fields.
|
|
392
|
+
status: "ready" as const,
|
|
393
|
+
ready: true,
|
|
394
|
+
data: {
|
|
395
|
+
messages: {} as Record<string, ApxMessage[]>,
|
|
396
|
+
},
|
|
379
397
|
session: {
|
|
380
398
|
current: currentSession,
|
|
381
399
|
messages: messagesFor,
|
|
@@ -40,7 +40,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|
|
40
40
|
const apxModelKey = () => args.model || "apx-default"
|
|
41
41
|
const apxProvider = () => ({
|
|
42
42
|
id: "apx",
|
|
43
|
-
|
|
43
|
+
// Shown as the gray label in the prompt status line: "Build · model · APX Code".
|
|
44
|
+
name: "APX Code",
|
|
44
45
|
models: {
|
|
45
46
|
[apxModelKey()]: {
|
|
46
47
|
id: apxModelKey(),
|
|
@@ -10,18 +10,18 @@
|
|
|
10
10
|
*
|
|
11
11
|
* The APX-tailored sidebar shows session / agent / model / context usage.
|
|
12
12
|
*/
|
|
13
|
-
import { For, Show, createMemo,
|
|
14
|
-
import {
|
|
13
|
+
import { For, Show, createMemo, createEffect, onCleanup } from "solid-js"
|
|
14
|
+
import { MouseButton } from "@opentui/core"
|
|
15
15
|
import { useTerminalDimensions } from "@opentui/solid"
|
|
16
16
|
import { useTheme } from "@tui/context/theme"
|
|
17
17
|
import { useRoute } from "@tui/context/route"
|
|
18
18
|
import { useApxSync } from "@tui/context/sync-apx"
|
|
19
19
|
import { useSDK } from "@tui/context/sdk-apx"
|
|
20
20
|
import { useLocal } from "@tui/context/local"
|
|
21
|
-
import {
|
|
21
|
+
import { Toast } from "@tui/ui/toast"
|
|
22
22
|
import { useDialog } from "@tui/ui/dialog"
|
|
23
|
-
import { useExit } from "@tui/context/exit"
|
|
24
23
|
import { usePromptRef } from "@tui/context/prompt"
|
|
24
|
+
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
|
25
25
|
import type { ApxMessage, ApxPart } from "@tui/context/sync-apx"
|
|
26
26
|
import { SidebarApx } from "./sidebar-apx"
|
|
27
27
|
import { MessageActions } from "./message-actions"
|
|
@@ -39,36 +39,80 @@ function parseError(raw: string): { message: string; trace?: string; hint?: stri
|
|
|
39
39
|
return { message, trace, hint }
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
// Coding modes (mirrors web Code). Only Build is wired today; Plan/Zen are
|
|
43
|
+
// shown as the active label is "Build" until a mode toggle lands.
|
|
44
|
+
const MODES = ["Build", "Plan", "Zen"] as const
|
|
45
|
+
|
|
46
|
+
const TOOL_LABELS: Record<string, string> = {
|
|
47
|
+
read_file: "Read",
|
|
48
|
+
write_file: "Write",
|
|
49
|
+
edit_file: "Edit",
|
|
50
|
+
search_files: "Search",
|
|
51
|
+
list_files: "List",
|
|
52
|
+
run_shell: "Shell",
|
|
53
|
+
load_skill: "Skill",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const MAX_DIFF_LINES = 24
|
|
57
|
+
|
|
58
|
+
/** Git-style diff block: removed lines in red (-), added lines in green (+). */
|
|
59
|
+
function DiffBlock(props: { search?: string; replace?: string; content?: string }) {
|
|
60
|
+
const { theme } = useTheme()
|
|
61
|
+
const removed = () => (props.search ? props.search.replace(/\n$/, "").split("\n") : [])
|
|
62
|
+
const added = () => ((props.replace ?? props.content) ? (props.replace ?? props.content ?? "").replace(/\n$/, "").split("\n") : [])
|
|
63
|
+
const shown = () => {
|
|
64
|
+
const r = removed().map((t) => ({ sign: "-", t, color: theme.error }))
|
|
65
|
+
const a = added().map((t) => ({ sign: "+", t, color: theme.success }))
|
|
66
|
+
const all = [...r, ...a]
|
|
67
|
+
return all.length > MAX_DIFF_LINES
|
|
68
|
+
? [...all.slice(0, MAX_DIFF_LINES), { sign: " ", t: `… ${all.length - MAX_DIFF_LINES} more lines`, color: theme.textMuted }]
|
|
69
|
+
: all
|
|
55
70
|
}
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
return (
|
|
72
|
+
<box flexDirection="column" marginLeft={2} marginTop={0} backgroundColor={theme.backgroundPanel} paddingLeft={1} paddingRight={1}>
|
|
73
|
+
<For each={shown()}>
|
|
74
|
+
{(line) => (
|
|
75
|
+
<text color={line.color}>
|
|
76
|
+
{line.sign} {line.t}
|
|
77
|
+
</text>
|
|
78
|
+
)}
|
|
79
|
+
</For>
|
|
80
|
+
</box>
|
|
81
|
+
)
|
|
58
82
|
}
|
|
59
83
|
|
|
60
84
|
function ToolPart(props: { part: Extract<ApxPart, { kind: "tool" }> }) {
|
|
61
85
|
const { theme } = useTheme()
|
|
62
86
|
const color = () => (props.part.running ? theme.warning ?? theme.primary : props.part.ok === false ? theme.error : theme.success)
|
|
63
87
|
const marker = () => (props.part.running ? "▸" : props.part.ok === false ? "✗" : "→")
|
|
88
|
+
const label = () => TOOL_LABELS[props.part.name] ?? props.part.name
|
|
89
|
+
const a = () => (props.part.args && typeof props.part.args === "object" ? (props.part.args as any) : {})
|
|
90
|
+
const target = () => a().path ?? a().filePath ?? a().file ?? a().pattern ?? a().query ?? a().command ?? ""
|
|
91
|
+
const isEdit = () => props.part.name === "edit_file" && (a().search || a().replace)
|
|
92
|
+
const isWrite = () => props.part.name === "write_file" && a().content
|
|
64
93
|
return (
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
94
|
+
<box flexDirection="column">
|
|
95
|
+
<text color={color()}>
|
|
96
|
+
{marker()} {label()}
|
|
97
|
+
{target() ? " " + (String(target()).length > 60 ? String(target()).slice(0, 57) + "…" : target()) : ""}
|
|
98
|
+
</text>
|
|
99
|
+
<Show when={isEdit()}>
|
|
100
|
+
<DiffBlock search={a().search} replace={a().replace} />
|
|
101
|
+
</Show>
|
|
102
|
+
<Show when={isWrite()}>
|
|
103
|
+
<DiffBlock content={a().content} />
|
|
104
|
+
</Show>
|
|
105
|
+
</box>
|
|
68
106
|
)
|
|
69
107
|
}
|
|
70
108
|
|
|
71
|
-
function AssistantBubble(props: {
|
|
109
|
+
function AssistantBubble(props: {
|
|
110
|
+
msg: ApxMessage
|
|
111
|
+
onActivate: () => void
|
|
112
|
+
agentName: string
|
|
113
|
+
modelLabel: string
|
|
114
|
+
mode: string
|
|
115
|
+
}) {
|
|
72
116
|
const { theme, syntax } = useTheme()
|
|
73
117
|
const parts = createMemo<ApxPart[]>(() => {
|
|
74
118
|
const p = props.msg.parts
|
|
@@ -77,9 +121,19 @@ function AssistantBubble(props: { msg: ApxMessage; onActivate: () => void }) {
|
|
|
77
121
|
return props.msg.text ? [{ kind: "text", text: props.msg.text }] : []
|
|
78
122
|
})
|
|
79
123
|
const empty = () => parts().length === 0
|
|
124
|
+
// OpenCode-style meta line shown after the answer: mode · model · response time.
|
|
125
|
+
const elapsed = () => {
|
|
126
|
+
if (!props.msg.completedAt) return ""
|
|
127
|
+
return `${((props.msg.completedAt - props.msg.createdAt) / 1000).toFixed(1)}s`
|
|
128
|
+
}
|
|
129
|
+
const meta = () => {
|
|
130
|
+
const parts = [props.mode, props.msg.model || props.modelLabel, elapsed()].filter(Boolean)
|
|
131
|
+
return parts.join(" · ")
|
|
132
|
+
}
|
|
80
133
|
return (
|
|
81
134
|
<box
|
|
82
135
|
flexDirection="column"
|
|
136
|
+
marginTop={1}
|
|
83
137
|
marginBottom={1}
|
|
84
138
|
paddingLeft={2}
|
|
85
139
|
paddingRight={2}
|
|
@@ -88,8 +142,8 @@ function AssistantBubble(props: { msg: ApxMessage; onActivate: () => void }) {
|
|
|
88
142
|
if (e?.button === undefined || e.button === MouseButton.LEFT) props.onActivate()
|
|
89
143
|
}}
|
|
90
144
|
>
|
|
91
|
-
<text color={theme.success} bold>
|
|
92
|
-
{props.msg.streaming ? `${props.
|
|
145
|
+
<text color={theme.success} bold marginBottom={1}>
|
|
146
|
+
{props.msg.streaming ? `${props.agentName} ▸` : props.agentName}
|
|
93
147
|
</text>
|
|
94
148
|
<Show when={!empty()} fallback={<text color={theme.textMuted}>…</text>}>
|
|
95
149
|
<box flexDirection="column">
|
|
@@ -120,6 +174,12 @@ function AssistantBubble(props: { msg: ApxMessage; onActivate: () => void }) {
|
|
|
120
174
|
</For>
|
|
121
175
|
</box>
|
|
122
176
|
</Show>
|
|
177
|
+
<Show when={!props.msg.streaming && !props.msg.error}>
|
|
178
|
+
<box flexDirection="row" marginTop={1}>
|
|
179
|
+
<text color={theme.primary}>■ </text>
|
|
180
|
+
<text color={theme.textMuted}>{meta()}</text>
|
|
181
|
+
</box>
|
|
182
|
+
</Show>
|
|
123
183
|
</box>
|
|
124
184
|
)
|
|
125
185
|
}
|
|
@@ -127,19 +187,31 @@ function AssistantBubble(props: { msg: ApxMessage; onActivate: () => void }) {
|
|
|
127
187
|
function UserBubble(props: { msg: ApxMessage; onActivate: () => void }) {
|
|
128
188
|
const { theme } = useTheme()
|
|
129
189
|
const queued = () => props.msg.queued === true
|
|
190
|
+
const accent = () => (queued() ? theme.textMuted : theme.primary)
|
|
130
191
|
return (
|
|
131
192
|
<box
|
|
132
193
|
flexDirection="row"
|
|
194
|
+
marginTop={1}
|
|
133
195
|
marginBottom={1}
|
|
196
|
+
paddingLeft={2}
|
|
134
197
|
paddingRight={2}
|
|
135
198
|
onMouseDown={(e: any) => {
|
|
136
199
|
// Left click (or plain click w/o button info) opens Message Actions.
|
|
137
200
|
if (e?.button === undefined || e.button === MouseButton.LEFT) props.onActivate()
|
|
138
201
|
}}
|
|
139
202
|
>
|
|
140
|
-
{/*
|
|
141
|
-
<box width={1} backgroundColor={
|
|
142
|
-
<box
|
|
203
|
+
{/* single colored accent bar on the left + filled background (OpenCode style) */}
|
|
204
|
+
<box width={1} backgroundColor={accent()} flexShrink={0} />
|
|
205
|
+
<box
|
|
206
|
+
flexDirection="column"
|
|
207
|
+
flexGrow={1}
|
|
208
|
+
minWidth={0}
|
|
209
|
+
paddingLeft={2}
|
|
210
|
+
paddingRight={2}
|
|
211
|
+
paddingTop={1}
|
|
212
|
+
paddingBottom={1}
|
|
213
|
+
backgroundColor={theme.backgroundElement}
|
|
214
|
+
>
|
|
143
215
|
<text color={queued() ? theme.textMuted : theme.text} wrap>
|
|
144
216
|
{props.msg.text}
|
|
145
217
|
</text>
|
|
@@ -197,12 +269,12 @@ export function Session() {
|
|
|
197
269
|
const sync = useApxSync()
|
|
198
270
|
const sdk = useSDK()
|
|
199
271
|
const local = useLocal()
|
|
200
|
-
const toast = useToast()
|
|
201
272
|
const dialog = useDialog()
|
|
202
|
-
const exit = useExit()
|
|
203
273
|
const promptRef = usePromptRef()
|
|
204
|
-
|
|
205
|
-
|
|
274
|
+
|
|
275
|
+
// Show the sidebar only on wide terminals; on narrow ones the chat keeps
|
|
276
|
+
// full width (the directory/branch live in the sidebar, shown when wide).
|
|
277
|
+
const wide = createMemo(() => dims().width >= 100)
|
|
206
278
|
|
|
207
279
|
// Bridge the user's /models selection into the SDK so the next turn uses it.
|
|
208
280
|
createEffect(() => {
|
|
@@ -215,8 +287,27 @@ export function Session() {
|
|
|
215
287
|
return sync.session.current() ?? ""
|
|
216
288
|
})
|
|
217
289
|
|
|
290
|
+
// Keep the sync store's "current session" pinned to the session we're viewing
|
|
291
|
+
// so the sidebar/usage track the right bucket.
|
|
292
|
+
createEffect(() => {
|
|
293
|
+
const id = sessionID()
|
|
294
|
+
if (id) sync.session.setCurrent(id)
|
|
295
|
+
})
|
|
296
|
+
|
|
218
297
|
const messages = createMemo(() => sync.session.messages(sessionID()))
|
|
219
298
|
|
|
299
|
+
// Active mode + model, shown after each answer (mode · model · response time).
|
|
300
|
+
const mode = createMemo(() => MODES[0]) // Build (Plan/Zen toggle: future work)
|
|
301
|
+
const modelLabel = createMemo(() => {
|
|
302
|
+
const parsed = local.model?.parsed?.()
|
|
303
|
+
if (parsed?.modelID) return parsed.providerID ? `${parsed.providerID}:${parsed.modelID}` : parsed.modelID
|
|
304
|
+
return sdk.model || "—"
|
|
305
|
+
})
|
|
306
|
+
const agentName = createMemo(() => {
|
|
307
|
+
const a = sdk.agent || "Assistant"
|
|
308
|
+
return a.charAt(0).toUpperCase() + a.slice(1)
|
|
309
|
+
})
|
|
310
|
+
|
|
220
311
|
onCleanup(() => {
|
|
221
312
|
promptRef.set(undefined)
|
|
222
313
|
})
|
|
@@ -225,78 +316,6 @@ export function Session() {
|
|
|
225
316
|
dialog.replace(() => <MessageActions sessionID={sessionID()} message={msg} />)
|
|
226
317
|
}
|
|
227
318
|
|
|
228
|
-
function makeRef(r: TextareaRenderable) {
|
|
229
|
-
return {
|
|
230
|
-
get focused() {
|
|
231
|
-
return r.focused
|
|
232
|
-
},
|
|
233
|
-
get current() {
|
|
234
|
-
return { input: r.plainText, parts: [] as any[] }
|
|
235
|
-
},
|
|
236
|
-
set(prompt: { input: string; parts: any[] }) {
|
|
237
|
-
r.setText(prompt.input)
|
|
238
|
-
},
|
|
239
|
-
reset() {
|
|
240
|
-
r.clear()
|
|
241
|
-
},
|
|
242
|
-
blur() {
|
|
243
|
-
r.blur()
|
|
244
|
-
},
|
|
245
|
-
focus() {
|
|
246
|
-
r.focus()
|
|
247
|
-
},
|
|
248
|
-
submit() {
|
|
249
|
-
void handleSubmit()
|
|
250
|
-
},
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async function handleSubmit() {
|
|
255
|
-
if (!inputEl) return
|
|
256
|
-
const text = inputEl.plainText.trim()
|
|
257
|
-
if (!text) return
|
|
258
|
-
|
|
259
|
-
if (text === "exit" || text === "quit" || text === ":q") {
|
|
260
|
-
void exit()
|
|
261
|
-
return
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
inputEl.clear()
|
|
265
|
-
|
|
266
|
-
// Shell command
|
|
267
|
-
if (text.startsWith("!") && text.length > 1) {
|
|
268
|
-
try {
|
|
269
|
-
await sync.runShell(text.slice(1).trim())
|
|
270
|
-
} catch (e) {
|
|
271
|
-
toast.error(e instanceof Error ? e : new Error(String(e)))
|
|
272
|
-
}
|
|
273
|
-
return
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// A turn is already in flight → queue it (OpenCode behaviour).
|
|
277
|
-
if (sending()) {
|
|
278
|
-
const id = sync.queueMessage(text)
|
|
279
|
-
if (!id) toast.show({ message: "No hay sesión activa todavía", variant: "warning" })
|
|
280
|
-
else toast.show({ message: "Mensaje en cola", variant: "info" })
|
|
281
|
-
return
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
setSending(true)
|
|
285
|
-
try {
|
|
286
|
-
await sync.sendMessage(text)
|
|
287
|
-
// Flush any messages queued while this turn was streaming.
|
|
288
|
-
let next = messages().find((m) => m.queued)
|
|
289
|
-
while (next) {
|
|
290
|
-
await sync.sendQueued(sessionID(), next.id)
|
|
291
|
-
next = messages().find((m) => m.queued)
|
|
292
|
-
}
|
|
293
|
-
} catch (e) {
|
|
294
|
-
toast.error(e instanceof Error ? e : new Error(String(e)))
|
|
295
|
-
} finally {
|
|
296
|
-
setSending(false)
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
319
|
return (
|
|
301
320
|
<box flexDirection="column" flexGrow={1} width={dims().width} height={dims().height}>
|
|
302
321
|
<box flexDirection="row" flexGrow={1} minHeight={0}>
|
|
@@ -319,7 +338,15 @@ export function Session() {
|
|
|
319
338
|
if (msg.role === "user") return <UserBubble msg={msg} onActivate={() => openActions(msg)} />
|
|
320
339
|
if (msg.role === "shell") return <ShellBubble msg={msg} />
|
|
321
340
|
if (msg.error) return <ErrorBubble msg={msg} />
|
|
322
|
-
return
|
|
341
|
+
return (
|
|
342
|
+
<AssistantBubble
|
|
343
|
+
msg={msg}
|
|
344
|
+
onActivate={() => openActions(msg)}
|
|
345
|
+
agentName={agentName()}
|
|
346
|
+
modelLabel={modelLabel()}
|
|
347
|
+
mode={mode()}
|
|
348
|
+
/>
|
|
349
|
+
)
|
|
323
350
|
}}
|
|
324
351
|
</For>
|
|
325
352
|
</Show>
|
|
@@ -327,40 +354,28 @@ export function Session() {
|
|
|
327
354
|
</box>
|
|
328
355
|
</scrollbox>
|
|
329
356
|
|
|
330
|
-
{/* Input
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
setTimeout(() => setTimeout(() => handleSubmit(), 0), 0)
|
|
346
|
-
}}
|
|
347
|
-
/>
|
|
348
|
-
</box>
|
|
349
|
-
<box height={1} paddingLeft={2} paddingRight={2} justifyContent="space-between" flexDirection="row">
|
|
350
|
-
<Show
|
|
351
|
-
when={sending()}
|
|
352
|
-
fallback={<text color={theme.textMuted}>enter send · ! shell · click msg for actions · exit quit</text>}
|
|
353
|
-
>
|
|
354
|
-
<text color={theme.warning ?? theme.primary} italic>
|
|
355
|
-
▸ Streaming… (enter queues your next message)
|
|
356
|
-
</text>
|
|
357
|
-
</Show>
|
|
358
|
-
</box>
|
|
357
|
+
{/* Input — the OpenCode prompt component (colored box, Build/Plan mode
|
|
358
|
+
selector, model label, Enter-to-send). With sessionID set it streams
|
|
359
|
+
to the *current* session via session.prompt instead of creating a
|
|
360
|
+
new one. This is also what fixes Enter submission and focus, since
|
|
361
|
+
the prompt owns the keymap submit wiring. */}
|
|
362
|
+
<box flexShrink={0} paddingLeft={1} paddingRight={1} paddingTop={1} paddingBottom={1}>
|
|
363
|
+
<Prompt
|
|
364
|
+
sessionID={sessionID()}
|
|
365
|
+
visible={true}
|
|
366
|
+
ref={(r?: PromptRef) => promptRef.set(r)}
|
|
367
|
+
placeholders={{
|
|
368
|
+
normal: ["Ask anything…", "Fix a TODO in the codebase", "Explain this code"],
|
|
369
|
+
shell: ["ls -la", "git status", "pwd"],
|
|
370
|
+
}}
|
|
371
|
+
/>
|
|
359
372
|
</box>
|
|
360
373
|
</box>
|
|
361
374
|
|
|
362
|
-
{/* Sidebar */}
|
|
363
|
-
<
|
|
375
|
+
{/* Sidebar — only on wide terminals; carries the directory + branch */}
|
|
376
|
+
<Show when={wide()}>
|
|
377
|
+
<SidebarApx sessionID={sessionID()} />
|
|
378
|
+
</Show>
|
|
364
379
|
</box>
|
|
365
380
|
<Toast />
|
|
366
381
|
</box>
|