Merge #4054 into stage-mj

This commit is contained in:
nesquena-hermes
2026-06-12 20:14:42 +00:00
3 changed files with 35 additions and 7 deletions

View File

@@ -949,8 +949,11 @@ async function cmdUse(args){
}
return;
}
const directive = `[USER OVERRIDE] You MUST consult skill '${match.name}' via skill_view before responding to the next message.`;
resolve(directive);
const detail = await api(`/api/skills/content?name=${encodeURIComponent(match.name)}`);
const skillContent = detail&&typeof detail.content==='string' ? detail.content.trim() : '';
if(!skillContent) throw new Error(`Skill \`${match.name}\` has no readable content.`);
const directive = `[USER OVERRIDE] You MUST follow the skill '${match.name}' content provided below before responding to the next message.`;
resolve({name:match.name,directive,content:skillContent});
if(isCurrentSession()){
S.messages.push({role:'assistant', content:`Next turn: skill \`${match.name}\` will be forced.`});
renderMessages();

View File

@@ -1063,10 +1063,22 @@ async function send(){
if(_forcedSkillDirectivePending){
const _pending=_forcedSkillDirectivePending;
if(!_pending.sessionId||_pending.sessionId===activeSid){
const _directive = await _pending.promise;
const _directivePayload = await _pending.promise;
if(_forcedSkillDirectivePending===_pending)_forcedSkillDirectivePending = null;
if(typeof _directive==='string'&&_directive){
msgText=`${_directive}\n\n${msgText||''}`.trim();
if(_directivePayload){
const _directive = typeof _directivePayload==='string'
? _directivePayload
: String(_directivePayload.directive||'').trim();
const _forcedSkillName = typeof _directivePayload==='string'
? ''
: String(_directivePayload.name||'').trim();
const _forcedSkillContent = typeof _directivePayload==='string'
? ''
: String(_directivePayload.content||'').trim();
const _forcedSkillBlock = _forcedSkillName&&_forcedSkillContent
? `[FORCED SKILL CONTEXT: ${_forcedSkillName}]\n${_forcedSkillContent}\n[/FORCED SKILL CONTEXT]`
: '';
msgText=`${_directive}${_forcedSkillBlock?`\n\n${_forcedSkillBlock}`:''}\n\n${msgText||''}`.trim();
}
}
}

View File

@@ -34,6 +34,8 @@ def test_forced_skill_directive_set_in_cmdUse():
src = read("static/commands.js")
assert "pending.promise = new Promise" in src, "cmdUse must create a pending Promise"
assert "_forcedSkillDirectivePending = pending;" in src, "cmdUse must publish the pending directive before awaiting"
assert "resolve({name:match.name,directive,content:skillContent});" in src, \
"cmdUse must resolve the pending payload with skill name, directive, and fetched content"
def test_use_entry_has_noEcho():
@@ -59,7 +61,7 @@ def test_directive_consumed_at_injection_site():
finally_part = src.split("finally")[1] if "finally" in src else ""
assert "_forcedSkillDirectivePending = null;" not in finally_part, \
"_forcedSkillDirectivePending must NOT be cleared in the finally block"
assert "const _directive = await _pending.promise;" in src, \
assert "const _directivePayload = await _pending.promise;" in src, \
"consume site must await the pending promise"
assert "_forcedSkillDirectivePending = null;" in src, \
"_forcedSkillDirectivePending must be cleared somewhere in messages.js"
@@ -75,7 +77,16 @@ def test_directive_injection_before_empty_guard():
def test_directive_text_uses_match_name():
src = read("static/commands.js")
assert "match.name" in src, "directive must use match.name (canonical casing), not raw user input"
assert "[USER OVERRIDE] You MUST consult skill '" in src, "directive text must match the specified format"
assert "[USER OVERRIDE] You MUST follow the skill '" in src, "directive text must match the specified format"
assert "content provided below" in src, "directive must reference the injected skill content"
def test_use_fetches_canonical_skill_content():
src = read("static/commands.js")
assert "api(`/api/skills/content?name=${encodeURIComponent(match.name)}`)" in src, \
"cmdUse must fetch the canonical skill content after resolving the canonical skill name"
assert "typeof detail.content==='string' ? detail.content.trim() : ''" in src, \
"cmdUse must reject missing or non-string skill content"
def test_pending_promise_set_synchronously():
@@ -114,3 +125,5 @@ def test_directive_only_consumed_by_matching_session():
"send() must only consume /use directives issued for the active session"
assert "if(_forcedSkillDirectivePending===_pending)_forcedSkillDirectivePending = null;" in src, \
"send() must not clear a newer pending directive created while awaiting"
assert "[FORCED SKILL CONTEXT: ${_forcedSkillName}]" in src, \
"send() must prepend deterministic forced-skill content before the user message"