Skip to content

Commit 199cbe9

Browse files
authored
fix: make inline /command references actionable instead of dismissive (#121)
A slash command mentioned mid-message (e.g. "your /goal today is to /plan and build the page") does not auto-run, and the agent was leading its reply by reporting the commands as failed and telling the user to resend them — while separately doubting whether EnterPlanMode was even a callable tool. Strengthen the per-turn inline-command reminder and the standing system-prompt guidance to act on the intent each reference expresses: call the real EnterPlanMode tool for /plan (named explicitly as an available tool, not just prose), pursue the described objective for /goal, load /skill:<name> via ReadSkill, and apply equivalent guidance for other commands. Mention invoking the literal slash command only when genuinely needed. This is the agent-mediated fix for inline commands; mechanical message-splitting was rejected because it mis-attributes args and discards the real task.
1 parent bff54f9 commit 199cbe9

4 files changed

Lines changed: 28 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ GitHub Releases page; `0.8.0` is the new starting line.
1818
- **The in-app updater recovers from Homebrew's untrusted-tap refusal.** Homebrew 5.0 (`HOMEBREW_REQUIRE_TAP_TRUST`) refuses to load formulas from third-party taps until `brew trust <tap>` is run once, which made the in-app `brew upgrade` fail with only a generic "run manually" hint. The updater now detects both the hard `Refusing to load … from untrusted tap` refusal and the soft `Skipping … not trusted` warning, offers to run `brew trust pythoughts-labs/pythinker` and retry the upgrade once on an interactive terminal, and otherwise prints the exact remediation. It also catches the silent no-op where an untrusted tap is skipped during `brew update` and `brew upgrade` exits 0 without advancing the version.
1919
- **Session exports redact secrets surfaced by tool output.** A tool result (e.g. `grep`/`cat` over a `.env`) could write a secret value into an exported transcript in plaintext. `/export` now redacts the value of secret-named keys (`password`, `token`, `api_key`, `secret`, …) to `[REDACTED]` in both the markdown and YAML formats, while leaving non-secret keys such as `token_count`, usernames, and ports intact.
2020
- **The welcome logo's antenna blinks a fixed number of times on launch, then settles.** Replaces the terminal's indefinite slow-blink with a bounded boot animation — the antenna ball blinks seven times after the banner prints and then holds steady. It is skipped under reduced motion, on non-interactive output, and when the terminal is too short to keep the antenna row on screen.
21+
- **Inline `/command` references get acted on, not just explained away.** When a message mentions a slash command mid-sentence (e.g. "your `/goal` today is to `/plan` and build the page"), the command doesn't auto-run — but the agent no longer leads its reply by reporting it as failed. The per-turn reminder and the system prompt now steer the agent to act on the intent: call the real `EnterPlanMode` tool for `/plan` (clarified as a genuine, callable tool so models stop doubting it exists), pursue the described objective for `/goal`, load `/skill:<name>` via `ReadSkill`, and apply equivalent guidance for other commands — only surfacing how to invoke the literal command when genuinely needed.
2122

2223
## 0.41.0 (2026-06-11)
2324

src/pythinker_code/agents/default/system.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ For research or multimedia tasks (images, video, PDFs, docs, spreadsheets, prese
125125

126126
**Skills (`ReadSkill`).** Load a skill's exact instructions before applying its workflow — mandatory for `review-pr`, `diagnose-ci-failures`, `fix-errors`, `implement-specs`, `spec-driven-implementation`, `check-impl-against-spec`, `resolve-merge-conflicts`, and `create-pr`. Read skill details only when needed, to conserve context. Catalog and scope precedence in §12.
127127

128-
**Inline `/command` references.** Slash commands execute only as their own message starting with `/`. A `/command` or `/skill:<name>` mentioned mid-message did not run: treat the reference as part of the request — load a referenced skill via `ReadSkill`, apply referenced guidance yourself, or tell the user to invoke it as a standalone message. Never silently drop such a reference.
128+
**Inline `/command` references.** Slash commands execute only as their own message starting with `/`. A `/command` or `/skill:<name>` mentioned mid-message did not auto-run, but it still expresses intent — act on it rather than leading your reply by reporting it as failed. For `/plan`, call `EnterPlanMode` (a real tool in your toolset, not just prose); for `/goal`, pursue the described objective until it is verifiably done; for `/skill:<name>`, load it via `ReadSkill` and apply it; for other guidance commands, apply the equivalent guidance yourself. Mention invoking the real command only when genuinely needed. Never silently drop such a reference.
129129

130130
**MCP.** Connected MCP servers expose their capabilities as ordinary tools already in your toolset (descriptions name the server). To *use* one, invoke its tools directly — never pip-install the server, import it as a module, or search the repo for its config. If a named server has no tools present, it is not connected (loading, failed, or unauthorized), not missing: point the user to `/mcp` for status, and to `pythinker mcp auth <server_name>` for an unauthorized OAuth server.
131131

src/pythinker_code/soul/dynamic_injections/inline_commands.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@
2525

2626
_REMINDER_TEMPLATE = (
2727
"The user's message references slash commands inline: {refs}. Inline references do "
28-
"NOT execute — a slash command runs only as its own message starting with '/'. Do "
29-
"not silently ignore them; treat each reference as part of the request: for "
28+
"NOT execute as commands — a slash command runs only as its own message starting "
29+
"with '/'. Do not silently ignore them, and do not lead your reply by telling the "
30+
"user they failed to run. Instead act on the intent each reference expresses: for "
31+
"/plan, call the EnterPlanMode tool to enter plan mode before implementing (it is "
32+
"an available tool, not just prose); for /goal, treat the objective the user "
33+
"described as the goal to pursue across this work until it is verifiably done; for "
3034
"/skill:<name>, load that skill with ReadSkill and apply its instructions; for "
31-
"guidance-injecting commands (e.g. /best-practices), apply the closest equivalent "
32-
"guidance yourself and tell the user how to run the real command; otherwise tell "
33-
"the user the command did not run and how to invoke it."
35+
"other guidance-injecting commands (e.g. /best-practices), apply the closest "
36+
"equivalent guidance yourself. Only mention how to invoke the real slash command "
37+
"if doing so is genuinely needed to satisfy the request."
3438
)
3539

3640

tests/core/test_inline_command_provider.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def _mock_soul(is_subagent: bool = False) -> MagicMock:
2424
_cmd("best-practices", aliases=["bp"]),
2525
_cmd("clear"),
2626
_cmd("goal"),
27+
_cmd("plan"),
2728
]
2829
return soul
2930

@@ -42,6 +43,22 @@ async def test_injects_for_inline_known_command() -> None:
4243
assert "NOT execute" in result[0].content
4344

4445

46+
async def test_reminder_is_actionable_not_dismissive() -> None:
47+
"""The reminder must steer the agent to act on intent, not to lead by telling
48+
the user the command 'did not run'."""
49+
provider = InlineCommandReminderProvider()
50+
history = [_user("your /goal today is to /plan and build the landing page")]
51+
result = await provider.get_injections(history, _mock_soul())
52+
assert len(result) == 1
53+
content = result[0].content
54+
# Names the real EnterPlanMode tool so the agent stops doubting it exists.
55+
assert "EnterPlanMode" in content
56+
# Pursues the described objective for /goal rather than just explaining syntax.
57+
assert "goal to pursue" in content
58+
# Does not instruct the agent to report the commands as failed up front.
59+
assert "did not run" not in content
60+
61+
4562
async def test_injects_for_alias() -> None:
4663
provider = InlineCommandReminderProvider()
4764
history = [_user("apply /bp while you build it")]

0 commit comments

Comments
 (0)