@agentprojectcontext/apx 1.31.2 → 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/constants.js +5 -0
- 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 +66 -36
- 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/core/agent/tools/handlers/ask-questions.js +115 -0
- 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 +118 -1
- package/src/host/daemon/api/code.js +60 -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} +45 -6
- package/src/host/daemon/plugins/index.js +2 -2
- package/src/host/daemon/plugins/telegram/ask.js +309 -0
- package/src/host/daemon/plugins/{telegram.js → telegram/index.js} +390 -191
- 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/artifact.js +99 -0
- 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 +8 -4
- package/src/interfaces/cli/postinstall.js +2 -2
- package/src/interfaces/cli/terminal-chat/renderer.js +22 -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 +9 -9
- 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/AskQuestionsCard.tsx +72 -0
- package/src/interfaces/web/src/components/chat/InlineAskPanel.tsx +399 -0
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
- package/src/interfaces/web/src/components/chat/MessageList.tsx +2 -1
- package/src/interfaces/web/src/components/chat/SkillPicker.tsx +77 -0
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +230 -0
- package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +40 -17
- 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 +27 -7
- package/src/interfaces/web/src/i18n/es.ts +27 -7
- package/src/interfaces/web/src/lib/api/artifacts.ts +47 -0
- package/src/interfaces/web/src/lib/api/skills.ts +25 -0
- package/src/interfaces/web/src/lib/api.ts +2 -0
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +41 -20
- 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 +27 -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/super-agent-tools/tools/ask-questions.js +0 -32
- package/src/host/daemon/tool-call-parser.js +0 -2
- package/src/interfaces/web/dist/assets/index-BDUsA6L6.css +0 -1
- package/src/interfaces/web/dist/assets/index-BV615I9p.js +0 -548
- package/src/interfaces/web/dist/assets/index-BV615I9p.js.map +0 -1
- /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
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export const MAX_TOOL_ITERS = 6;
|
|
2
2
|
export const ACK_ONLY_TOOLS = new Set(["send_telegram"]);
|
|
3
3
|
export const MAX_CONSECUTIVE_ACKS = 2;
|
|
4
|
+
// Tools whose semantics REQUIRE handing control back to the user. After the
|
|
5
|
+
// tool runs we break the loop — even under completionContract — because the
|
|
6
|
+
// task literally cannot advance without a human reply. Without this, models
|
|
7
|
+
// under forced toolChoice spam the same question across iterations.
|
|
8
|
+
export const TURN_ENDING_TOOLS = new Set(["ask_questions"]);
|
package/src/core/agent/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { projectStorageRoot } from "
|
|
4
|
-
import { getOrCreateApxId } from "
|
|
3
|
+
import { projectStorageRoot } from "../config/index.js";
|
|
4
|
+
import { getOrCreateApxId } from "../apc/scaffold.js";
|
|
5
5
|
|
|
6
6
|
const EMPTY_MEMORY = (slug) =>
|
|
7
7
|
`# Memory — ${slug}\n\n` +
|
|
@@ -128,42 +128,6 @@ export function fallbackModels(globalConfig) {
|
|
|
128
128
|
.filter((m) => typeof m === "string" && m.includes(":"));
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
/**
|
|
132
|
-
* @deprecated use fallbackModels(). Kept for tests / external callers that
|
|
133
|
-
* still ask "what provider to try after Ollama?". Derives the answer from the
|
|
134
|
-
* resolved model chain.
|
|
135
|
-
*/
|
|
136
|
-
export function fallbackOrder(globalConfig) {
|
|
137
|
-
const models = fallbackModels(globalConfig);
|
|
138
|
-
const providers = [];
|
|
139
|
-
for (const m of models) {
|
|
140
|
-
try {
|
|
141
|
-
const p = parseModelId(m).provider;
|
|
142
|
-
if (!providers.includes(p)) providers.push(p);
|
|
143
|
-
} catch { /* skip malformed entries */ }
|
|
144
|
-
}
|
|
145
|
-
return providers.length ? providers : [...DEFAULT_FALLBACK_ORDER];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* @deprecated use fallbackModels(). Looks up a single provider's model in
|
|
150
|
-
* the resolved chain. Returns "" if the provider isn't in the fallback list.
|
|
151
|
-
*/
|
|
152
|
-
export function modelForProvider(globalConfig, provider) {
|
|
153
|
-
const p = String(provider).toLowerCase();
|
|
154
|
-
const sa = globalConfig?.super_agent || {};
|
|
155
|
-
const models = fallbackModels(globalConfig);
|
|
156
|
-
const match = models.find((m) => {
|
|
157
|
-
try { return parseModelId(m).provider === p; } catch { return false; }
|
|
158
|
-
});
|
|
159
|
-
if (match) return match;
|
|
160
|
-
// Ollama gets a special fallback to the primary model (legacy behavior).
|
|
161
|
-
if (p === "ollama" && typeof sa.model === "string" && /^ollama:/i.test(sa.model)) {
|
|
162
|
-
return sa.model;
|
|
163
|
-
}
|
|
164
|
-
return DEFAULT_FALLBACK_MODELS[p] || "";
|
|
165
|
-
}
|
|
166
|
-
|
|
167
131
|
export function isFallbackEnabled(globalConfig) {
|
|
168
132
|
const fb = globalConfig?.super_agent?.model_fallback || {};
|
|
169
133
|
return fb.enabled !== false;
|
|
@@ -243,15 +207,29 @@ export async function resolveActiveModel(globalConfig, { overrideModel = null, t
|
|
|
243
207
|
}
|
|
244
208
|
|
|
245
209
|
export async function probeAllProviders(globalConfig, timeoutMs) {
|
|
246
|
-
const
|
|
210
|
+
const models = fallbackModels(globalConfig);
|
|
211
|
+
// Build a deduped list of {provider, model} in chain order. Fall back to
|
|
212
|
+
// DEFAULT_FALLBACK_ORDER + DEFAULT_FALLBACK_MODELS when nothing is configured.
|
|
213
|
+
const entries = [];
|
|
214
|
+
const seen = new Set();
|
|
215
|
+
for (const m of models) {
|
|
216
|
+
let provider;
|
|
217
|
+
try { provider = parseModelId(m).provider; } catch { continue; }
|
|
218
|
+
if (seen.has(provider)) continue;
|
|
219
|
+
seen.add(provider);
|
|
220
|
+
entries.push({ provider, model: m });
|
|
221
|
+
}
|
|
222
|
+
if (entries.length === 0) {
|
|
223
|
+
for (const provider of DEFAULT_FALLBACK_ORDER) {
|
|
224
|
+
const m = DEFAULT_FALLBACK_MODELS[provider];
|
|
225
|
+
entries.push({ provider, model: m || "(not set)" });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
247
229
|
const out = [];
|
|
248
|
-
for (const provider of
|
|
230
|
+
for (const { provider, model } of entries) {
|
|
249
231
|
const health = await checkProviderHealth(provider, globalConfig, timeoutMs);
|
|
250
|
-
out.push({
|
|
251
|
-
provider,
|
|
252
|
-
model: modelForProvider(globalConfig, provider) || "(not set)",
|
|
253
|
-
...health,
|
|
254
|
-
});
|
|
232
|
+
out.push({ provider, model, ...health });
|
|
255
233
|
}
|
|
256
234
|
return out;
|
|
257
235
|
}
|
|
@@ -7,13 +7,20 @@
|
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { readIdentity } from "../identity.js";
|
|
10
|
+
import { readIdentity } from "../identity/index.js";
|
|
11
11
|
import { readSelfMemoryForPrompt } from "./self-memory.js";
|
|
12
|
+
import { buildSkillsHintBlock } from "./skills/catalog.js";
|
|
12
13
|
|
|
13
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
const PROMPTS_DIR = path.join(__dirname, "prompts");
|
|
15
16
|
const BASE_PROMPT_PATH = path.join(PROMPTS_DIR, "super-agent-base.md");
|
|
16
17
|
|
|
18
|
+
// Action discipline + chit-chat rules — same file used by project agents
|
|
19
|
+
// (buildAgentSystem in agent-system.js), loaded once here for the super-agent.
|
|
20
|
+
const ACTION_DISCIPLINE = fs
|
|
21
|
+
.readFileSync(path.join(PROMPTS_DIR, "action-discipline.md"), "utf8")
|
|
22
|
+
.trimEnd();
|
|
23
|
+
|
|
17
24
|
const promptCache = new Map();
|
|
18
25
|
|
|
19
26
|
/** @deprecated use super-agent-base.md */
|
|
@@ -24,15 +31,15 @@ const LEGACY_PROMPT_PATH = path.join(PROMPTS_DIR, "super-agent-default.md");
|
|
|
24
31
|
// turn is channel "deck" + voice mode, not its own channel.
|
|
25
32
|
const CHANNEL_PROMPT_FILES = {
|
|
26
33
|
telegram: "channels/telegram.md",
|
|
27
|
-
terminal: "channels/terminal.md",
|
|
28
34
|
cli: "channels/cli.md",
|
|
29
35
|
routine: "channels/routine.md",
|
|
30
36
|
api: "channels/api.md",
|
|
31
37
|
web: "channels/web.md", // web big chat (long-form, full tools)
|
|
32
38
|
web_sidebar: "channels/web_sidebar.md", // web quick chat (short, lightweight)
|
|
39
|
+
web_code: "channels/web_code.md", // web Code module (rich coding session)
|
|
33
40
|
deck: "channels/deck.md", // cockpit dashboard
|
|
34
41
|
desktop: "channels/desktop.md", // PC floating module (was "overlay")
|
|
35
|
-
code: "channels/code.md", //
|
|
42
|
+
code: "channels/code.md", // `apx code` — terminal coding session
|
|
36
43
|
};
|
|
37
44
|
|
|
38
45
|
// Voice mode: spoken-reply rules layered on any surface when the turn will be
|
|
@@ -135,7 +142,7 @@ export function buildIdentityBlock(identity, userLang = "en") {
|
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
// "Who you're talking to" block. Agent-agnostic: built once from the resolved
|
|
138
|
-
// sender (see core/telegram
|
|
145
|
+
// sender (see core/identity/telegram.js) and injected into BOTH the super-agent
|
|
139
146
|
// prompt and any routed project-agent prompt, so identification doesn't depend
|
|
140
147
|
// on which agent answers. Returns "" when there's no sender info.
|
|
141
148
|
export function buildRelationshipBlock(sender) {
|
|
@@ -173,8 +180,8 @@ export function buildRelationshipBlock(sender) {
|
|
|
173
180
|
return lines.join("\n");
|
|
174
181
|
}
|
|
175
182
|
|
|
176
|
-
//
|
|
177
|
-
// when empty so the block is dropped entirely.
|
|
183
|
+
// The super-agent's own notebook (~/.apx/memory.md), bounded for the prompt.
|
|
184
|
+
// Returns "" when empty so the block is dropped entirely.
|
|
178
185
|
export function buildSelfMemoryBlock() {
|
|
179
186
|
const slice = readSelfMemoryForPrompt();
|
|
180
187
|
if (!slice) return "";
|
|
@@ -193,59 +200,6 @@ export function isSuperAgentEnabled(cfg) {
|
|
|
193
200
|
return sa.enabled !== false;
|
|
194
201
|
}
|
|
195
202
|
|
|
196
|
-
function buildPermissionBlock(sa) {
|
|
197
|
-
const permissionMode = sa.permission_mode || "automatico";
|
|
198
|
-
const allowedTools = Array.isArray(sa.allowed_tools) ? sa.allowed_tools : [];
|
|
199
|
-
return [
|
|
200
|
-
"# Permission mode",
|
|
201
|
-
`mode: ${permissionMode}`,
|
|
202
|
-
`allowed_tools: ${allowedTools.join(", ") || "(none)"}`,
|
|
203
|
-
"When a tool schema has confirmed, set confirmed=true only after explicit user confirmation for that exact action.",
|
|
204
|
-
].join("\n");
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Skill descriptions are authored for Claude Code's skill matcher, so many end
|
|
208
|
-
// with verbose "Trigger on: …" / "Activate when …" lists and multi-sentence
|
|
209
|
-
// usage notes. Inside Roby's prompt those tails are pure noise (he matches
|
|
210
|
-
// semantically, not by trigger string). Keep the first sentence only, drop the
|
|
211
|
-
// trigger/activation tail, and cap length — this is the single biggest
|
|
212
|
-
// signal-per-token win in the prompt (~1k tokens recovered per turn).
|
|
213
|
-
function condenseSkillDescription(desc) {
|
|
214
|
-
if (!desc) return "(no description)";
|
|
215
|
-
const full = String(desc).replace(/\s+/g, " ").trim();
|
|
216
|
-
const MARKER =
|
|
217
|
-
/\s*(?:Trigger(?:s)? on|Triggers|TRIGGER|Activate (?:on|when|only)|Use this skill (?:whenever|when)|Use (?:it )?when|Triggers include|SKIP|Also (?:use|triggers))\b/i;
|
|
218
|
-
// Prefer the gist before any trigger/activation marker; but if a skill leads
|
|
219
|
-
// straight into "Activate ONLY when…" (no gist first), that head is empty —
|
|
220
|
-
// fall back to the first sentence of the full text so we keep real info.
|
|
221
|
-
let d = full.split(MARKER)[0].trim();
|
|
222
|
-
if (d.length < 15) d = full;
|
|
223
|
-
// First sentence only, then cap length.
|
|
224
|
-
const firstStop = d.search(/\.(\s|$)/);
|
|
225
|
-
if (firstStop > 0) d = d.slice(0, firstStop + 1);
|
|
226
|
-
d = d.trim();
|
|
227
|
-
if (d.length > 160) d = d.slice(0, 157).trimEnd() + "…";
|
|
228
|
-
return d || "(no description)";
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function buildSkillsCatalog(listSkills) {
|
|
232
|
-
let list = [];
|
|
233
|
-
try {
|
|
234
|
-
list = listSkills();
|
|
235
|
-
} catch {
|
|
236
|
-
/* empty */
|
|
237
|
-
}
|
|
238
|
-
if (!list.length) return "";
|
|
239
|
-
return [
|
|
240
|
-
"# Available skills (load on demand)",
|
|
241
|
-
"Catalog (slug + one-line gist). Bodies are NOT loaded. When the user needs",
|
|
242
|
-
"knowledge or syntax matching one (match semantically, any language), call",
|
|
243
|
-
"load_skill({slug}).",
|
|
244
|
-
"",
|
|
245
|
-
...list.map((s) => `- **${s.slug}**: ${condenseSkillDescription(s.description)}`),
|
|
246
|
-
].join("\n");
|
|
247
|
-
}
|
|
248
|
-
|
|
249
203
|
export function buildSuperAgentSystem({
|
|
250
204
|
globalConfig,
|
|
251
205
|
projects,
|
|
@@ -263,12 +217,12 @@ export function buildSuperAgentSystem({
|
|
|
263
217
|
// at the end of the system prompt (where format directives belong),
|
|
264
218
|
// not mixed in with situational context.
|
|
265
219
|
systemSuffix = "",
|
|
266
|
-
// Pre-rendered output of the Memory Broker (Pieza 4): a [
|
|
220
|
+
// Pre-rendered output of the Memory Broker (Pieza 4): a [RELEVANT MEMORY]
|
|
267
221
|
// block built from the RAG retriever + recent memory.md entries. When
|
|
268
222
|
// provided it REPLACES the always-on self-memory slice (it already includes
|
|
269
223
|
// the latest notebook entries). "" falls back to the plain notebook slice.
|
|
270
224
|
memoryBlock = "",
|
|
271
|
-
// Pre-rendered "#
|
|
225
|
+
// Pre-rendered "# Active threads on other channels" block (recency-based
|
|
272
226
|
// cross-channel awareness; see core/memory/active-threads.js). "" → omitted.
|
|
273
227
|
activeThreadsBlock = "",
|
|
274
228
|
// Compact "# Tools adicionales (activación on-demand)" block: instructions +
|
|
@@ -304,14 +258,14 @@ export function buildSuperAgentSystem({
|
|
|
304
258
|
memoryBlock || buildSelfMemoryBlock(),
|
|
305
259
|
activeThreadsBlock,
|
|
306
260
|
relationshipBlock,
|
|
307
|
-
buildPermissionBlock(sa),
|
|
308
261
|
extraContext,
|
|
309
262
|
"# Registered projects (just the index — call tools for details)",
|
|
310
263
|
projectIndex || "(no projects registered)",
|
|
311
264
|
buildProjectAgentsBlock(channelMeta?.projectPath),
|
|
312
|
-
|
|
265
|
+
buildSkillsHintBlock(listSkills),
|
|
313
266
|
lazyToolsBlock,
|
|
314
267
|
voiceModeBlock,
|
|
268
|
+
ACTION_DISCIPLINE,
|
|
315
269
|
systemSuffix,
|
|
316
270
|
]
|
|
317
271
|
.filter(Boolean)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Action Discipline (mandatory)
|
|
2
|
+
- NEVER acknowledge an action without executing it in the same turn. If you are going to do something, call the tool FIRST, then report the result.
|
|
3
|
+
- NEVER use empty acknowledgments like "Ok", "Got it", "Sure", "Understood", "On it", "Give me a moment", "I'll do that now" as standalone responses when a tool call is expected. These are invalid responses.
|
|
4
|
+
- Action first, report after. Produce the tool call in the same response as your acknowledgment.
|
|
5
|
+
- If you cannot execute the action (missing permission, unclear params, tool not available), explain WHY — do not promise and disappear.
|
|
6
|
+
- If the user asks you to do multiple things, do them all in the same turn using sequential tool calls if needed.
|
|
7
|
+
|
|
8
|
+
## One reply per turn — no repeated greetings (mandatory)
|
|
9
|
+
- A single turn can produce SEVERAL text segments: a short narration you write BEFORE calling a tool, and the final answer that comes AFTER the tool runs. On some surfaces each segment is shown separately.
|
|
10
|
+
- Greet AT MOST ONCE per turn. If you already said "hola"/"hi" in an early segment, do NOT greet again in the final answer — start it with the actual content.
|
|
11
|
+
- NEVER repeat the same sentence, greeting, or summary across segments of the same turn. Each segment is shown in full.
|
|
12
|
+
- On simple requests, SKIP the intro entirely: go straight to the work, then give the result once. Only add a short intro when the work will clearly take more than a single quick tool call, and keep it to a few words ("un momento…", "reviso eso…").
|
|
13
|
+
|
|
14
|
+
## Chit-chat & greetings (only path out of a forced tool turn)
|
|
15
|
+
- If the user is just greeting, chatting, or thanking you with NO actionable request ("hola", "hi", "buenas", "gracias", "👍", "ok"), you must STILL satisfy the tool-choice contract: call `finish` with a brief friendly reply in the user's language. Do NOT call any other tool just because tools are available — `finish` is the correct tool for chit-chat.
|
|
16
|
+
- A greeting that piggybacks a real request ("hola, listame las rutinas") is NOT chit-chat — handle the request normally with the right tool.
|
|
17
|
+
- When in doubt between chit-chat and a vague request, ask ONE short clarifying question via `finish` — never invent a topic or run an unrelated tool to "be useful".
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
# Channel context
|
|
2
|
-
Channel: **code** (the
|
|
2
|
+
Channel: **code** (`apx code`, the interactive APX coding session in the terminal) — the same OpenCode-style coding surface as the web Code module, just rendered in the terminal. You can read, search, edit files and run shell commands.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
All file and shell tools resolve relative to that project path — operate inside it unless told otherwise.
|
|
4
|
+
- CWD: {{cwd}}
|
|
5
|
+
- References to "this directory", "this project", "here", "current folder" mean the CWD above — use it as the path argument; do not ask for a path
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Working style — KEEP GOING UNTIL THE TASK IS DONE:
|
|
7
|
+
Working style — KEEP GOING UNTIL THE TASK IS DONE (build mode):
|
|
11
8
|
- You are an autonomous coding agent. Once the user gives a task, complete the WHOLE thing in this turn: chain as many tool calls as needed (read → edit → run → verify), do not stop after one or two steps.
|
|
12
|
-
- NEVER stop to ask "do you want me to…?" / "
|
|
9
|
+
- NEVER stop to ask "do you want me to…?" / "should I continue?" / "is that OK?". You already have permission on this surface — just do it. Only ask if the task is truly ambiguous and you genuinely cannot proceed.
|
|
13
10
|
- NEVER announce an action and then end your turn ("now I'll edit the file." → stop). If you say you will do something, immediately call the tool and actually do it in the same turn.
|
|
14
|
-
- After each tool result, decide the next concrete step and take it. Keep iterating until the
|
|
11
|
+
- After each tool result, decide the next concrete step and take it. Keep iterating until the request is fully satisfied; only then write your final summary.
|
|
15
12
|
- If something fails, read the error, fix it, and retry — don't hand the problem back to the user.
|
|
16
13
|
|
|
17
14
|
Formatting:
|
|
18
|
-
- Markdown
|
|
19
|
-
- Lead with the result
|
|
20
|
-
- When you edit files, prefer small, surgical `edit_file` changes over rewriting whole files, and explain the intent of each change briefly.
|
|
15
|
+
- Markdown OK; keep readable; use code diffs when editing.
|
|
16
|
+
- Lead with the result; keep prose tight. Don't re-paste full tool output the user can already see.
|
|
@@ -16,8 +16,9 @@ Formatting:
|
|
|
16
16
|
done and a tiny confirmation, not an explanation.
|
|
17
17
|
- No markdown tables, no code fences, no bulleted lists unless the user
|
|
18
18
|
explicitly asks. Plain prose only — these get read aloud verbatim.
|
|
19
|
-
- No URLs / file paths spelled out — refer to them by name ("
|
|
20
|
-
|
|
19
|
+
- No URLs / file paths spelled out — refer to them by name (e.g. "open
|
|
20
|
+
Voices in the web admin" rather than "http://localhost:7430/m/voice").
|
|
21
|
+
Use the user's language when phrasing it.
|
|
21
22
|
- If a Voice mode block is present below, its rules win over anything here.
|
|
22
23
|
- Bias hard toward DOING the action and reporting the result in one breath,
|
|
23
24
|
rather than asking back. Confirm-after, not confirm-before, for
|
|
@@ -27,8 +28,9 @@ Don't repeat yourself (this matters — your messages are shown AND spoken):
|
|
|
27
28
|
- Greet AT MOST once per conversation. If you already said hi, never greet
|
|
28
29
|
again — jump straight to the answer.
|
|
29
30
|
- When you call a tool, any line BEFORE it must be a 2–4 word filler only
|
|
30
|
-
("
|
|
31
|
-
the result before the tool has run — you
|
|
31
|
+
(e.g. "one moment…", "checking that…", in the user's language). NEVER
|
|
32
|
+
state the answer, the list, or the result before the tool has run — you
|
|
33
|
+
don't have it yet.
|
|
32
34
|
- After the tool returns, give the result ONCE. Do not re-announce it, do not
|
|
33
35
|
re-greet, do not restate the filler. One clean reply.
|
|
34
36
|
- Never say the same thing twice across a single turn.
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
# Channel context
|
|
2
2
|
Channel: **routine** (autonomous scheduled run — not an interactive chat).
|
|
3
3
|
|
|
4
|
-
Routine: `{{routineName}}`
|
|
4
|
+
Routine name: `{{routineName}}`
|
|
5
|
+
Routine ID: `{{routineId}}`
|
|
6
|
+
Schedule: `{{routineSchedule}}`
|
|
7
|
+
Last run: {{routineLastRun}}
|
|
8
|
+
Routine memory: `{{routineMemoryPath}}`
|
|
5
9
|
Project path: {{projectPath}}
|
|
6
10
|
|
|
11
|
+
## Routine memory (durable notes for this routine)
|
|
12
|
+
{{routineMemory}}
|
|
13
|
+
|
|
7
14
|
Formatting:
|
|
8
15
|
- Execute the task fully; no conversational filler
|
|
9
16
|
- This is not Telegram — do not optimize for chat brevity unless the routine prompt says so
|
|
17
|
+
- "Last run" is when this routine fired previously — use it to know what's already been done; never repeat the same work blindly
|
|
18
|
+
- "Routine memory" above is everything this specific routine has decided to remember across runs. Treat as known facts. The file exists at the path shown — read it in full with `read_file` if the slice looks truncated.
|
|
@@ -7,3 +7,8 @@ Formatting:
|
|
|
7
7
|
- Previous turns are conversational context only; re-call tools for facts
|
|
8
8
|
|
|
9
9
|
What the user sees here: ONLY your final text reply. They do NOT see your tool calls, args, or intermediate results — those never reach Telegram. So if a request needs real work (running something, searching, editing, a multi-step task), the channel sends a short "on it" heads-up for you; you still must report what you actually did in plain words at the end. Never assume they saw what you ran.
|
|
10
|
+
|
|
11
|
+
Segments policy: when you write any prose BEFORE calling a tool (an intro like "voy a revisar…") it lands as its OWN Telegram message — separate from the final answer that comes AFTER the tool runs. So:
|
|
12
|
+
- Greet at most ONCE per turn. If you already said "Hola" in the intro segment, do NOT greet again in the final answer. Start the final answer with the actual content.
|
|
13
|
+
- Prefer to skip the intro entirely on simple requests — go straight to the work, then answer. Only add an intro when the work will take noticeably longer than a single tool call.
|
|
14
|
+
- Never repeat the same sentence across segments — each message is shown in full to the user.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Channel context
|
|
2
|
+
Channel: **web_code** (the Code module in the web admin panel at `/m/code`) — a rich, OpenCode-style coding session scoped to a single project. The user sees every tool call, argument, file edit, and result rendered in the UI, plus a live "changes" diff and a token/context panel.
|
|
3
|
+
|
|
4
|
+
Working project: **{{projectName}}** (id {{projectId}})
|
|
5
|
+
Path: `{{projectPath}}`
|
|
6
|
+
All file and shell tools resolve relative to that project path — operate inside it unless told otherwise.
|
|
7
|
+
|
|
8
|
+
{{modeGuidance}}
|
|
9
|
+
|
|
10
|
+
Working style — KEEP GOING UNTIL THE TASK IS DONE:
|
|
11
|
+
- You are an autonomous coding agent. Once the user gives a task, complete the WHOLE thing in this turn: chain as many tool calls as needed (read → edit → run → verify), do not stop after one or two steps.
|
|
12
|
+
- NEVER stop to ask "do you want me to…?" / "should I continue?" / "is that OK?". You already have permission on this surface — just do it. Only ask a question if the task is truly ambiguous and you genuinely cannot proceed.
|
|
13
|
+
- NEVER announce an action and then end your turn ("now I'll edit the file." → stop). If you say you will do something, immediately call the tool and actually do it in the same turn.
|
|
14
|
+
- After each tool result, decide the next concrete step and take it. Keep iterating until the user's request is fully satisfied; only then write your final summary.
|
|
15
|
+
- If something fails, read the error, fix it, and retry — don't hand the problem back to the user.
|
|
16
|
+
|
|
17
|
+
Formatting:
|
|
18
|
+
- Markdown with code fences is expected. Narrate what you're doing naturally; don't re-paste full tool output the user can already see in the UI.
|
|
19
|
+
- Lead with the result. Keep prose tight — this is a working surface, not a chat.
|
|
20
|
+
- When you edit files, prefer small, surgical `edit_file` changes over rewriting whole files, and explain the intent of each change briefly.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
# Voice mode (your reply will be SPOKEN by a TTS engine)
|
|
2
2
|
- Two short sentences max. No markdown, no bullet lists, no code fences, no URLs — it gets read aloud.
|
|
3
|
-
- Lead with the outcome ("
|
|
4
|
-
- Spell things out for the ear: say "
|
|
3
|
+
- Lead with the outcome (e.g. "Done, the task is noted"), then at most one detail. Phrase it in the user's language.
|
|
4
|
+
- Spell things out for the ear: say "dot" instead of ".", avoid long ids/paths — summarize them (e.g. "the apx project").
|
|
@@ -19,8 +19,8 @@ You also ship **apx-\* skills** with the exact syntax for multi-step APX operati
|
|
|
19
19
|
|
|
20
20
|
# Memory & history
|
|
21
21
|
You have durable memory across all channels — never deny it. Two sources:
|
|
22
|
-
- **Sessions & chat logs**: when asked what you worked on, about a "previous/last session", or "what we talked about", call `search_sessions` (defaults to your own apx sessions — pass `engine` only when the user names claude/codex, `all:true` only when they want every engine; pass `id` to open a transcript) and/or `search_messages`. Answer didactically in prose ("
|
|
23
|
-
- **Your notebook (self-memory)**: `~/.apx/memory.md`, a bounded slice injected above (as "# Your notebook" or folded into "#
|
|
22
|
+
- **Sessions & chat logs**: when asked what you worked on, about a "previous/last session", or "what we talked about", call `search_sessions` (defaults to your own apx sessions — pass `engine` only when the user names claude/codex, `all:true` only when they want every engine; pass `id` to open a transcript) and/or `search_messages`. Answer didactically in prose (in the user's language — e.g. "last time we worked on X and Y"), not as a raw list of titles. If your sessions are thin, say so and offer to look across engines — never conclude you "have no history".
|
|
23
|
+
- **Your notebook (self-memory)**: `~/.apx/memory.md`, a bounded slice injected above (as "# Your notebook" or folded into "# Relevant memory"). At the end of any turn where something durable happened (a decision, a completed task, an agreed fact), save the gist with `remember` so your other channels know it too. Keep notes to one self-contained sentence. Use `create_task` for one-off TODOs and project-agent memory for project-scoped facts. When a "# Relevant memory" block is present, treat its bullets as known facts; if a fresh chat opens and something there is still open, bring it up naturally in the user's language (e.g. "yesterday we were on X — shall we continue?") — weave in only what's relevant, don't dump the block.
|
|
24
24
|
|
|
25
25
|
# How you operate
|
|
26
26
|
- APC projects live anywhere on disk; the default workspace is APX home, not a user repo. Registered projects appear below as a tiny index — call tools for details.
|
|
@@ -2,16 +2,10 @@ import { callEngine } from "../engines/index.js";
|
|
|
2
2
|
import {
|
|
3
3
|
extractPseudoToolCalls,
|
|
4
4
|
cleanTextOfPseudoToolCalls,
|
|
5
|
-
} from "./tool-call-parser.js";
|
|
5
|
+
} from "./tools/tool-call-parser.js";
|
|
6
6
|
import { resolveActiveModel, fallbackModels } from "./model-router.js";
|
|
7
|
-
import { MAX_TOOL_ITERS, ACK_ONLY_TOOLS, MAX_CONSECUTIVE_ACKS } from "./constants.js";
|
|
8
|
-
import {
|
|
9
|
-
isShortConfirmation,
|
|
10
|
-
lastAssistantAskedForConfirmation,
|
|
11
|
-
isGhostResponse,
|
|
12
|
-
looksLikeActionRequest,
|
|
13
|
-
} from "./ghost-guard.js";
|
|
14
|
-
import { pseudoToolSystem, shouldRetryWithPseudoTools } from "./pseudo-tools.js";
|
|
7
|
+
import { MAX_TOOL_ITERS, ACK_ONLY_TOOLS, MAX_CONSECUTIVE_ACKS, TURN_ENDING_TOOLS } from "./constants.js";
|
|
8
|
+
import { pseudoToolSystem, shouldRetryWithPseudoTools } from "./tools/pseudo-tools.js";
|
|
15
9
|
import { filterToolSchemas } from "./tools-overlap.js";
|
|
16
10
|
import { isRetryableEngineError, shortRetryReason } from "./retry.js";
|
|
17
11
|
|
|
@@ -39,6 +33,19 @@ function fallbackFinalText(trace, error) {
|
|
|
39
33
|
return lines.join("\n");
|
|
40
34
|
}
|
|
41
35
|
|
|
36
|
+
// A leading greeting clause: "¡Hola Manu!", "Hola,", "Hi there!", "Buenas tardes…".
|
|
37
|
+
// Intentionally narrow — only the opening salutation up to its first terminator —
|
|
38
|
+
// so we never eat real content.
|
|
39
|
+
const LEADING_GREETING_RE =
|
|
40
|
+
/^\s*[¡!]*\s*(hola+|holis?|buenas|buen[oa]s?\s+(d[ií]as|tardes|noches)|hey|hi|hello)\b[^.!?¡\n]*[.!?¡]*[\s,]*/i;
|
|
41
|
+
|
|
42
|
+
/** If `text` opens with a greeting, return it with that greeting removed; else null. */
|
|
43
|
+
function stripLeadingGreeting(text) {
|
|
44
|
+
const m = String(text).match(LEADING_GREETING_RE);
|
|
45
|
+
if (!m) return null;
|
|
46
|
+
return String(text).slice(m[0].length).replace(/^\s+/, "");
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
function previewTraceResult(result) {
|
|
43
50
|
if (result === null || result === undefined) return "ok";
|
|
44
51
|
if (typeof result === "string") return result.slice(0, 180);
|
|
@@ -160,11 +167,7 @@ export async function runAgent({
|
|
|
160
167
|
effectiveSchemas = [...effectiveSchemas, FINISH_TOOL_SCHEMA];
|
|
161
168
|
}
|
|
162
169
|
|
|
163
|
-
const rawHandlers = makeToolHandlers(
|
|
164
|
-
...toolHandlerCtx,
|
|
165
|
-
implicitConfirmation:
|
|
166
|
-
isShortConfirmation(prompt) && lastAssistantAskedForConfirmation(previousMessages),
|
|
167
|
-
});
|
|
170
|
+
const rawHandlers = makeToolHandlers(toolHandlerCtx);
|
|
168
171
|
const handlers = suppressed.size > 0
|
|
169
172
|
? new Proxy(rawHandlers, {
|
|
170
173
|
get(target, name) {
|
|
@@ -201,6 +204,22 @@ export async function runAgent({
|
|
|
201
204
|
const trace = [];
|
|
202
205
|
let totalUsage = { input_tokens: 0, output_tokens: 0 };
|
|
203
206
|
let lastText = "";
|
|
207
|
+
|
|
208
|
+
// Collapse repeated greetings within a single turn. A turn can produce several
|
|
209
|
+
// text segments (pre-tool narration + final answer) and weaker models greet in
|
|
210
|
+
// each one, so the user sees "¡Hola Manu!" twice. Keep the first greeting,
|
|
211
|
+
// strip any later one. Belt-and-suspenders over the action-discipline prompt
|
|
212
|
+
// rule (which strong models follow but gemini-flash et al. often ignore).
|
|
213
|
+
let greetedThisTurn = false;
|
|
214
|
+
const dedupeGreeting = (text) => {
|
|
215
|
+
if (!text) return text;
|
|
216
|
+
if (greetedThisTurn) {
|
|
217
|
+
const stripped = stripLeadingGreeting(text);
|
|
218
|
+
return stripped == null ? text : stripped;
|
|
219
|
+
}
|
|
220
|
+
if (LEADING_GREETING_RE.test(text)) greetedThisTurn = true;
|
|
221
|
+
return text;
|
|
222
|
+
};
|
|
204
223
|
let usePseudoTools = false;
|
|
205
224
|
let ackOnlyStreak = 0;
|
|
206
225
|
// Side-effect dedupe. Weaker models (Gemini especially) sometimes
|
|
@@ -258,16 +277,9 @@ export async function runAgent({
|
|
|
258
277
|
// Merge any tools activated via discover_tools on the previous iteration.
|
|
259
278
|
drainPendingTools();
|
|
260
279
|
await emitProgress(onEvent, { type: "model_start", iteration: iter + 1, model: activeModel });
|
|
261
|
-
// Force a tool call on iter 0 ONLY when the user message looks like a real
|
|
262
|
-
// action request ("listame…", "mandá…", "buscá…"). For chit-chat ("hola",
|
|
263
|
-
// "qué tal") forcing a tool makes weaker models (llama-3.3 via Groq,
|
|
264
|
-
// qwen3-32b) emit a malformed tool_calls payload — Groq then rejects the
|
|
265
|
-
// whole turn with 400 "Failed to call a function". Better: let the model
|
|
266
|
-
// choose between text and tool when the prompt is conversational.
|
|
267
280
|
const forceTool =
|
|
268
281
|
effectiveSchemas.length > 0 &&
|
|
269
282
|
(useContract ||
|
|
270
|
-
(iter === 0 && looksLikeActionRequest(prompt)) ||
|
|
271
283
|
(ackOnlyStreak > 0 && ackOnlyStreak <= MAX_CONSECUTIVE_ACKS));
|
|
272
284
|
let result;
|
|
273
285
|
try {
|
|
@@ -320,22 +332,11 @@ export async function runAgent({
|
|
|
320
332
|
}
|
|
321
333
|
|
|
322
334
|
if (!toolCalls || toolCalls.length === 0) {
|
|
323
|
-
if (iter === 0 && isGhostResponse(lastText) && looksLikeActionRequest(prompt)) {
|
|
324
|
-
await emitProgress(onEvent, { type: "ghost_response_detected", text: lastText });
|
|
325
|
-
conversation.push({ role: "assistant", content: lastText });
|
|
326
|
-
conversation.push({
|
|
327
|
-
role: "user",
|
|
328
|
-
content:
|
|
329
|
-
"Remember: you must execute the action, not just confirm it. " +
|
|
330
|
-
"Call the tool now — action first, report after.",
|
|
331
|
-
});
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
335
|
lastText = cleanTextOfPseudoToolCalls(lastText) || lastText;
|
|
335
336
|
break;
|
|
336
337
|
}
|
|
337
338
|
|
|
338
|
-
const visibleText = cleanTextOfPseudoToolCalls(lastText).trim();
|
|
339
|
+
const visibleText = dedupeGreeting(cleanTextOfPseudoToolCalls(lastText).trim());
|
|
339
340
|
if (visibleText) {
|
|
340
341
|
await emitProgress(onEvent, { type: "assistant_text", text: visibleText, iteration: iter + 1 });
|
|
341
342
|
}
|
|
@@ -347,6 +348,7 @@ export async function runAgent({
|
|
|
347
348
|
});
|
|
348
349
|
|
|
349
350
|
let finishSummary = null;
|
|
351
|
+
let turnEndingQuestions = null;
|
|
350
352
|
for (const tc of toolCalls) {
|
|
351
353
|
const fn = tc.function || tc;
|
|
352
354
|
const name = fn.name;
|
|
@@ -409,18 +411,45 @@ export async function runAgent({
|
|
|
409
411
|
tool_name: name,
|
|
410
412
|
content: JSON.stringify(toolResult),
|
|
411
413
|
});
|
|
414
|
+
|
|
415
|
+
// Capture turn-ending intents (e.g. ask_questions). The loop cannot
|
|
416
|
+
// legitimately advance without a user reply; under completionContract
|
|
417
|
+
// forcing another tool call just produces ask_questions spam.
|
|
418
|
+
if (TURN_ENDING_TOOLS.has(name) && !turnEndingQuestions) {
|
|
419
|
+
// Questions may be plain strings (legacy) or {question, options, ...}.
|
|
420
|
+
// For the assistant_text fallback we only need the prompt strings.
|
|
421
|
+
const qs = Array.isArray(args.questions)
|
|
422
|
+
? args.questions
|
|
423
|
+
.map((q) => (typeof q === "string" ? q : q && typeof q.question === "string" ? q.question : null))
|
|
424
|
+
.filter(Boolean)
|
|
425
|
+
: [];
|
|
426
|
+
turnEndingQuestions = qs;
|
|
427
|
+
}
|
|
412
428
|
}
|
|
413
429
|
|
|
414
430
|
// Task declared complete via the contract — emit the summary as the final
|
|
415
431
|
// assistant text and exit the loop.
|
|
416
432
|
if (finishSummary !== null) {
|
|
417
433
|
if (finishSummary) {
|
|
418
|
-
lastText = finishSummary;
|
|
419
|
-
await emitProgress(onEvent, { type: "assistant_text", text:
|
|
434
|
+
lastText = dedupeGreeting(finishSummary) || "";
|
|
435
|
+
if (lastText) await emitProgress(onEvent, { type: "assistant_text", text: lastText, iteration: iter + 1 });
|
|
420
436
|
}
|
|
421
437
|
break;
|
|
422
438
|
}
|
|
423
439
|
|
|
440
|
+
// ask_questions (or future turn-ending tools): the task is genuinely
|
|
441
|
+
// blocked on user input. Exit the loop — completionContract or not,
|
|
442
|
+
// asking again gets us nowhere. We deliberately do NOT emit a synthetic
|
|
443
|
+
// assistant_text and we leave lastText empty so persistence and one-shot
|
|
444
|
+
// API callers don't end up with a duplicate bullet list next to the
|
|
445
|
+
// rendering surfaces' own UI (web AskQuestionsCard, terminal renderer,
|
|
446
|
+
// telegram inline keyboard). The structured questions live on the tool
|
|
447
|
+
// trace — that's the canonical source.
|
|
448
|
+
if (turnEndingQuestions) {
|
|
449
|
+
if (!lastText) lastText = "";
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
|
|
424
453
|
const allAckOnly = toolCalls.every((tc) => {
|
|
425
454
|
const n = (tc.function?.name) || tc.name;
|
|
426
455
|
return ACK_ONLY_TOOLS.has(n);
|
|
@@ -434,7 +463,8 @@ export async function runAgent({
|
|
|
434
463
|
}
|
|
435
464
|
|
|
436
465
|
return {
|
|
437
|
-
|
|
466
|
+
// Strip a final greeting if an earlier segment in this turn already greeted.
|
|
467
|
+
text: dedupeGreeting(lastText),
|
|
438
468
|
usage: totalUsage,
|
|
439
469
|
name: agentName,
|
|
440
470
|
trace,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Prompt-shaped hint injected into external runtimes when APX delegates work
|
|
2
|
+
// to them (Claude Code, Codex, OpenCode, Aider, Cursor Agent, Gemini CLI,
|
|
3
|
+
// Qwen Code). Tells the runtime two things only:
|
|
4
|
+
//
|
|
5
|
+
// 1. "You're being orchestrated by APX as the super-agent (or as agent X
|
|
6
|
+
// via APX delegation)" — equivalent to a one-shot a2a hand-off.
|
|
7
|
+
// 2. The session id APX created on disk so the runtime can echo it back
|
|
8
|
+
// via `apx session close <id> --result "..."` if its shell allows it.
|
|
9
|
+
//
|
|
10
|
+
// We intentionally do NOT re-explain APC: every runtime that can read this
|
|
11
|
+
// hint already has the apc-context skill (or equivalent rule) installed in
|
|
12
|
+
// its own config, and that rule covers the project context. Repeating it
|
|
13
|
+
// here just bloats the prompt. The bridge is APX-specific glue.
|
|
14
|
+
//
|
|
15
|
+
// (Was `buildApfHint` in host/daemon/apc-runtime-context.js. "APF" was the
|
|
16
|
+
// old internal name; renamed for clarity. Lifecycle helpers
|
|
17
|
+
// — createRuntimeSession / closeRuntimeSession — live in
|
|
18
|
+
// core/stores/runtime-sessions.js.)
|
|
19
|
+
|
|
20
|
+
const RUNTIME_BRIDGE_HINT = `
|
|
21
|
+
# APX runtime delegation
|
|
22
|
+
|
|
23
|
+
You are being run by APX. The APX super-agent (or the named agent below) handed this turn to you as a one-shot delegation — think of it as an a2a (agent-to-agent) call where you are the callee. APX is the parent process; the project's apc-context already explains the codebase.
|
|
24
|
+
|
|
25
|
+
- Project: {{name}}
|
|
26
|
+
- Delegating agent: {{agent}}
|
|
27
|
+
- APX session id: {{session_id}}
|
|
28
|
+
|
|
29
|
+
When you finish, if you can shell out, leave a short trace for APX:
|
|
30
|
+
apx session close {{session_id}} --result "<one-line summary>"
|
|
31
|
+
`.trim();
|
|
32
|
+
|
|
33
|
+
export function buildRuntimeBridgeHint({ projectName, projectPath, agentSlug, sessionId }) {
|
|
34
|
+
return RUNTIME_BRIDGE_HINT
|
|
35
|
+
.replace(/\{\{name\}\}/g, projectName)
|
|
36
|
+
.replace(/\{\{path\}\}/g, projectPath)
|
|
37
|
+
.replace(/\{\{agent\}\}/g, agentSlug)
|
|
38
|
+
.replace(/\{\{session_id\}\}/g, sessionId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Back-compat alias — callers can migrate at their own pace.
|
|
42
|
+
export const buildApfHint = buildRuntimeBridgeHint;
|