@yeaft/webchat-agent 1.0.38 → 1.0.39
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 +1 -1
- package/yeaft/engine.js +50 -7
- package/yeaft/web-bridge.js +34 -0
package/package.json
CHANGED
package/yeaft/engine.js
CHANGED
|
@@ -95,6 +95,36 @@ const RETRY_DEFAULTS = Object.freeze({
|
|
|
95
95
|
jitterRatio: 0.25,
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
const EXPLICIT_SKILL_COMMAND_RE = /^\/skill:([^\s]+)(\s+|$)/;
|
|
99
|
+
|
|
100
|
+
function parseExplicitSkillCommand(prompt) {
|
|
101
|
+
if (typeof prompt !== 'string') {
|
|
102
|
+
return { skillName: null, cleanedPrompt: prompt };
|
|
103
|
+
}
|
|
104
|
+
const match = prompt.match(EXPLICIT_SKILL_COMMAND_RE);
|
|
105
|
+
if (!match) {
|
|
106
|
+
return { skillName: null, cleanedPrompt: prompt };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
skillName: match[1],
|
|
110
|
+
cleanedPrompt: prompt.slice(match[0].length),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function stripLeadingSkillCommandFromPromptParts(promptParts) {
|
|
115
|
+
if (!Array.isArray(promptParts) || promptParts.length === 0) return promptParts;
|
|
116
|
+
let stripped = false;
|
|
117
|
+
return promptParts.map(part => {
|
|
118
|
+
if (stripped || !part || part.type !== 'text' || typeof part.text !== 'string') {
|
|
119
|
+
return part;
|
|
120
|
+
}
|
|
121
|
+
const parsed = parseExplicitSkillCommand(part.text);
|
|
122
|
+
if (!parsed.skillName) return part;
|
|
123
|
+
stripped = true;
|
|
124
|
+
return { ...part, text: parsed.cleanedPrompt };
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
98
128
|
function resolveRetryPolicy(config) {
|
|
99
129
|
const raw = config?.llmRetry || {};
|
|
100
130
|
const num = (v, d) => (Number.isFinite(v) && v >= 0 ? v : d);
|
|
@@ -932,13 +962,21 @@ export class Engine {
|
|
|
932
962
|
* @param {string} [args.sessionAnnouncement]
|
|
933
963
|
* @param {string} [args.projectDoc] — resolved CLAUDE.md / AGENTS.md text (already truncated)
|
|
934
964
|
* @param {object} [args.taskCtx] — legacy task-context sub-block (optional)
|
|
965
|
+
* @param {string} [args.explicitSkillName] — leading /skill:<name> command, if present
|
|
935
966
|
* @returns {string}
|
|
936
967
|
*/
|
|
937
|
-
#buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectDoc, taskCtx, activeTasks } = {}) {
|
|
938
|
-
// Get relevant skill content if SkillManager is wired
|
|
968
|
+
#buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectDoc, taskCtx, activeTasks, explicitSkillName } = {}) {
|
|
969
|
+
// Get relevant skill content if SkillManager is wired. A leading
|
|
970
|
+
// /skill:<name> is explicit, not relevance matching: load that skill by
|
|
971
|
+
// name or inject a visible prompt warning when the command is unknown.
|
|
939
972
|
let skillContent = '';
|
|
940
|
-
if (this.#skillManager
|
|
941
|
-
|
|
973
|
+
if (this.#skillManager) {
|
|
974
|
+
if (explicitSkillName) {
|
|
975
|
+
skillContent = this.#skillManager.getPromptContent(explicitSkillName)
|
|
976
|
+
|| `## Skill command error\n\nRequested skill "${explicitSkillName}" was not found. Continue without that skill and tell the user it is unavailable.`;
|
|
977
|
+
} else if (prompt) {
|
|
978
|
+
skillContent = this.#skillManager.getRelevantPromptContent(prompt);
|
|
979
|
+
}
|
|
942
980
|
}
|
|
943
981
|
|
|
944
982
|
// Get tool names from the appropriate source
|
|
@@ -1583,7 +1621,11 @@ export class Engine {
|
|
|
1583
1621
|
// the merge, so an invalid caller value (e.g. 'ULTRA') does not shadow a
|
|
1584
1622
|
// valid prompt prefix.
|
|
1585
1623
|
const parsed = parseEffortPrefix(prompt);
|
|
1586
|
-
const
|
|
1624
|
+
const parsedSkill = parseExplicitSkillCommand(parsed.cleanedPrompt);
|
|
1625
|
+
const effectivePrompt = parsedSkill.cleanedPrompt;
|
|
1626
|
+
const effectivePromptParts = parsedSkill.skillName
|
|
1627
|
+
? stripLeadingSkillCommandFromPromptParts(promptParts)
|
|
1628
|
+
: promptParts;
|
|
1587
1629
|
const configuredEffort = normalizeEffort(this.#config?.modelEffort);
|
|
1588
1630
|
const effectiveUserEffort = normalizeEffort(userEffort) || parsed.effort || configuredEffort || null;
|
|
1589
1631
|
const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
|
|
@@ -1623,7 +1665,7 @@ export class Engine {
|
|
|
1623
1665
|
|
|
1624
1666
|
try {
|
|
1625
1667
|
this.#currentThreadId = threadId || MAIN_THREAD_ID;
|
|
1626
|
-
yield* this.#runQuery({ prompt: effectivePrompt, promptParts, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, sessionTopics, vpPlan, sessionAnnouncement, workDir, userAlreadyPersisted, getCurrentTodos, setCurrentTodos, threadId: this.#currentThreadId, drainPendingUserMessages, collabToolPolicy: effectiveCollabToolPolicy });
|
|
1668
|
+
yield* this.#runQuery({ prompt: effectivePrompt, promptParts: effectivePromptParts, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, sessionTopics, vpPlan, sessionAnnouncement, workDir, userAlreadyPersisted, getCurrentTodos, setCurrentTodos, threadId: this.#currentThreadId, drainPendingUserMessages, collabToolPolicy: effectiveCollabToolPolicy, explicitSkillName: parsedSkill.skillName });
|
|
1627
1669
|
} finally {
|
|
1628
1670
|
if (signal) {
|
|
1629
1671
|
try { signal.removeEventListener('abort', onExternalAbort); } catch { /* ignore */ }
|
|
@@ -1667,7 +1709,7 @@ export class Engine {
|
|
|
1667
1709
|
* in a try/finally without indenting the whole loop.
|
|
1668
1710
|
* @private
|
|
1669
1711
|
*/
|
|
1670
|
-
async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, sessionTopics = null, vpPlan, sessionAnnouncement, workDir, userAlreadyPersisted = false, getCurrentTodos = null, setCurrentTodos = null, threadId = MAIN_THREAD_ID, drainPendingUserMessages = null, collabToolPolicy = null }) {
|
|
1712
|
+
async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, sessionTopics = null, vpPlan, sessionAnnouncement, workDir, userAlreadyPersisted = false, getCurrentTodos = null, setCurrentTodos = null, threadId = MAIN_THREAD_ID, drainPendingUserMessages = null, collabToolPolicy = null, explicitSkillName = null }) {
|
|
1671
1713
|
|
|
1672
1714
|
const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
|
|
1673
1715
|
? collabToolPolicy
|
|
@@ -1783,6 +1825,7 @@ export class Engine {
|
|
|
1783
1825
|
sessionAnnouncement,
|
|
1784
1826
|
projectDoc,
|
|
1785
1827
|
activeTasks,
|
|
1828
|
+
explicitSkillName,
|
|
1786
1829
|
});
|
|
1787
1830
|
|
|
1788
1831
|
// ─── HARD INVARIANT: Compact ≠ Dream (read DESIGN-COMPACT-VS-DREAM.md) ─
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -73,6 +73,8 @@ import { buildMcpFlattenedTools } from './tools/mcp-tools.js';
|
|
|
73
73
|
import { getAgentRegistry, agentBelongsToScope } from './tools/agent.js';
|
|
74
74
|
import { isPromptableAgentStatus } from './sub-agent/status.js';
|
|
75
75
|
|
|
76
|
+
const SKILL_COMMAND_PREFIX = 'skill:';
|
|
77
|
+
|
|
76
78
|
/** @type {import('./session.js').Session | null} */
|
|
77
79
|
let session = null;
|
|
78
80
|
|
|
@@ -1690,6 +1692,37 @@ function sendSessionOutputFrame(data, { sessionId, chatId, vpId, turnId, threadI
|
|
|
1690
1692
|
});
|
|
1691
1693
|
}
|
|
1692
1694
|
|
|
1695
|
+
export function buildSkillSlashCommands(skillManager) {
|
|
1696
|
+
if (!skillManager || typeof skillManager.list !== 'function') return { commands: [], descriptions: {} };
|
|
1697
|
+
const commands = [];
|
|
1698
|
+
const descriptions = {};
|
|
1699
|
+
for (const skill of skillManager.list()) {
|
|
1700
|
+
if (!skill?.name || typeof skill.name !== 'string') continue;
|
|
1701
|
+
const commandName = `${SKILL_COMMAND_PREFIX}${skill.name}`;
|
|
1702
|
+
commands.push(commandName);
|
|
1703
|
+
descriptions[commandName] = skill.description || skill.trigger || 'Load Yeaft skill';
|
|
1704
|
+
}
|
|
1705
|
+
commands.sort((a, b) => a.localeCompare(b));
|
|
1706
|
+
return { commands, descriptions };
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function broadcastSkillSlashCommands(sessionLike) {
|
|
1710
|
+
const { commands, descriptions } = buildSkillSlashCommands(sessionLike?.skillManager);
|
|
1711
|
+
const slashCommands = [...new Set([...(ctx.slashCommands || []), ...commands])];
|
|
1712
|
+
const slashCommandDescriptions = {
|
|
1713
|
+
...(ctx.slashCommandDescriptions || {}),
|
|
1714
|
+
...descriptions,
|
|
1715
|
+
};
|
|
1716
|
+
ctx.slashCommands = slashCommands;
|
|
1717
|
+
ctx.slashCommandDescriptions = slashCommandDescriptions;
|
|
1718
|
+
sendToServer({
|
|
1719
|
+
type: 'slash_commands_update',
|
|
1720
|
+
conversationId: '__preload__',
|
|
1721
|
+
slashCommands,
|
|
1722
|
+
slashCommandDescriptions,
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1693
1726
|
/** Send a Yeaft Session metadata event over the legacy-compatible envelope. */
|
|
1694
1727
|
function sendSessionEvent(event, { sessionId, chatId, vpId, turnId, threadId } = {}) {
|
|
1695
1728
|
sendToServer({
|
|
@@ -3375,6 +3408,7 @@ async function ensureSessionLoaded() {
|
|
|
3375
3408
|
|
|
3376
3409
|
yeaftConversationId = `yeaft-${Date.now()}`;
|
|
3377
3410
|
hydrateYeaftStatusFromSession(session, { reason: 'session_ready', emitEvent: true });
|
|
3411
|
+
broadcastSkillSlashCommands(session);
|
|
3378
3412
|
|
|
3379
3413
|
// Per-group history is hydrated lazily on first `getOrCreateSessionHistory`
|
|
3380
3414
|
// — there's no global "all conversations" tape any more.
|