Skip to content

fix: workspace skill prompt injection and guidance for skill access t…#1051

Merged
omeraplak merged 4 commits into
mainfrom
fix/workspace-skill
Feb 13, 2026
Merged

fix: workspace skill prompt injection and guidance for skill access t…#1051
omeraplak merged 4 commits into
mainfrom
fix/workspace-skill

Conversation

@omeraplak
Copy link
Copy Markdown
Member

@omeraplak omeraplak commented Feb 11, 2026

…ools

PR Checklist

Please check if your PR fulfills the following requirements:

Bugs / Features

What is the current behavior?

What is the new behavior?

fixes (issue)

Notes for reviewers


Summary by cubic

Auto-inject a concise workspace skills prompt that lists metadata only and clearly guides agents to use skill tools; improve sandbox and filesystem tooling with better command normalization, quoted-arg handling, path context, structured outputs, and tool‑arg coercion. Infer allowed files from links in third‑party SKILL.md and update examples/docs. Fixes #1045.

  • New Features

    • Auto-inject workspace skills prompt by default; add workspaceSkillsPrompt to customize or disable.
    • Sandbox tooling: normalize full command lines into command+args and preserve quoted args; include path context in system prompt/tool descriptions; execute_command returns structured outputs; coerce stringified JSON tool args.
    • Improve third‑party skill compatibility by inferring references/scripts/assets from relative links in SKILL.md; examples and docs refreshed.
  • Bug Fixes

    • Inject only skill metadata (name, id, description); do not embed SKILL.md bodies.
    • Clarify skills system prompt to use workspace skill tools and avoid sandbox commands; tests added.

Written for commit 6ae8490. Summary will update on new commits.

Summary by CodeRabbit

  • Improvements

    • Activated skill prompts now include only metadata (name, id, description); full instructions are accessible via workspace skill tools.
    • Skill references/scripts/assets are inferred from instruction links when missing in skill metadata.
    • Skill descriptions truncated for consistent readability.
    • Sandbox commands are normalized (preserve quoted args); execution results and tool prompts include richer, structured details and path context.
  • New Features

    • Agents auto-inject a workspace skills prompt by default, configurable via a new workspaceSkillsPrompt option.
  • Documentation

    • Docs updated with auto-injection examples and disable/override guidance.
  • Tests

    • Added coverage for prompt injection, allowlist inference, and command normalization.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 11, 2026

🦋 Changeset detected

Latest commit: 6ae8490

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@voltagent/sandbox-daytona Patch
@voltagent/sandbox-e2b Patch
@voltagent/core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@joggrbot

This comment has been minimized.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Replaces full SKILL.md injection with metadata-only (name, id, description), instructs agents to use workspace skill tools in system prompts, infers skill file allowlists from instruction links, avoids per-skill async loads for activated lists, adds workspaceSkillsPrompt agent option, and normalizes/propagates sandbox command context.

Changes

Cohort / File(s) Summary
Workspace skills core
packages/core/src/workspace/skills/index.ts, .changeset/witty-planes-matter.md, .changeset/curly-ants-provide.md
Inject activated skills as metadata only (name/id/description); update SKILLS_SYSTEM_PROMPT to direct agents to workspace skill tools; infer references/scripts/assets from instruction links and merge into skill metadata; activated list rendering no longer performs per-skill async loads.
Agent hook, options & input coercion
packages/core/src/agent/agent.ts, packages/core/src/agent/types.ts, packages/core/src/agent/tool-input-coercion.ts, packages/core/src/agent/tool-input-coercion.spec.ts
Add AgentOptions.workspaceSkillsPrompt and resolve/compose a workspaceSkillsPrompt onPrepareMessages hook; implement coerceStringifiedJsonToolArgs and attempt coercion of stringified JSON tool args before throwing validation errors.
Workspace sandbox / toolkit path context
packages/core/src/workspace/sandbox/toolkit.ts, packages/core/src/workspace/index.ts, packages/core/src/workspace/filesystem/index.ts
Propagate pathContext into sandbox toolkit prompts and execute_command descriptions; include path context lines in system prompts; change execute_command to return structured execution results including eviction metadata; expose pathContext in WorkspaceSandboxToolkitContext.
Command normalization (sandbox)
packages/core/src/workspace/sandbox/command-normalization.ts, packages/core/src/workspace/sandbox/local.ts, packages/core/src/workspace/sandbox/index.ts, packages/sandbox-daytona/src/index.ts, packages/sandbox-e2b/src/index.ts, packages/core/src/workspace/sandbox/index.ts
Introduce normalizeCommandAndArgs and tokenization utilities; normalize full command-line strings into command + args across sandbox implementations and re-export the utility.
Tests — workspace & sandbox & agent
packages/core/src/workspace/skills/index.spec.ts, packages/core/src/agent/agent.spec.ts, packages/core/src/workspace/sandbox/local.spec.ts, packages/core/src/workspace/sandbox/toolkit.spec.ts, packages/core/src/agent/tool-input-coercion.spec.ts
Add tests for metadata-only prompt injection and workspaceSkillsPrompt behavior (default/skip/force), inferred allowlists from links, command normalization parsing and pathContext propagation, and tool-arg coercion.
Examples & docs
website/docs/workspaces/skills.md, examples/with-workspace/src/index.ts, examples/with-workspace/workspace/skills/playwright-cli/...
Docs and example updated to document auto prompt-injection and workspaceSkillsPrompt config; examples switched to use workspaceSkillsPrompt; large Playwright CLI skill added with many reference docs.
Toolkit type signature
packages/core/src/tool/toolkit.ts
Adjust Toolkit.tools type to accept an expanded Tool generic parameter (`Tool<ToolSchema, ToolSchema

Sequence Diagram(s)

sequenceDiagram
  participant Agent as Agent
  participant Workspace as Workspace
  participant Hook as WorkspaceSkillsHook
  participant AI as Model
  participant Tools as WorkspaceSkillTools

  Agent->>Workspace: initialize (workspaceSkillsPrompt option)
  Workspace->>Hook: build skills prompt (metadata + inferred allowlists)
  Hook->>Agent: onPrepareMessages adds <workspace_skills> system message
  Agent->>AI: send system + user messages
  AI->>Agent: response (may request tools)
  Agent->>Tools: call workspace_read_skill / workspace_read_skill_script / other tools
  Tools-->>Agent: tool outputs (files, scripts, assets)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • lzj960515

Poem

🐰 I hopped through SKILLs and left a trace,
Kept just the name, id, and a tidy grace.
Taught the agents which tools to call,
No more sandbox digging down the hall.
Hop, skip — prompts now clear for all!

🚥 Pre-merge checks | ✅ 5 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ⚠️ Unable to check for merge conflicts: Failed to fetch base branch: From https://github.com/VoltAgent/voltagent
! [rejected] main -> main (non-fast-forward)
+ 2fcc406...ec82442 main -> origin/main (forced update)
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly identifies the main change: auto-injecting workspace skill prompt with improved guidance for skill access, fixing the core issues from #1045.
Description check ✅ Passed The PR description follows the template with completed checklist items, references linked issue #1045, and includes a comprehensive summary of new features and bug fixes by cubic.
Linked Issues check ✅ Passed The PR comprehensively addresses all requirements from #1045: injects only skill metadata (name, id, description), adds explicit guidance to use workspace skill tools instead of sandbox commands, and improves documentation and examples.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue #1045: workspace skill prompt injection, sandbox tooling improvements with command normalization and structured outputs, third-party skill compatibility, and supporting docs/tests/examples.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/workspace-skill
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch fix/workspace-skill
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Feb 11, 2026

Deploying voltagent with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6ae8490
Status: ✅  Deploy successful!
Preview URL: https://66176d3b.voltagent.pages.dev
Branch Preview URL: https://fix-workspace-skill.voltagent.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 29 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/sandbox-daytona/src/index.ts">

<violation number="1" location="packages/sandbox-daytona/src/index.ts:64">
P2: tokenizeCommandLine drops empty quoted arguments, so commands like `cmd ""` lose the empty argument and change shell semantics. Preserve empty tokens when quotes are used.</violation>
</file>

<file name="packages/sandbox-e2b/src/index.ts">

<violation number="1" location="packages/sandbox-e2b/src/index.ts:81">
P2: Empty quoted arguments (e.g., `""` or `''`) are silently dropped by the tokenizer. `pushCurrent` skips tokens where `current.length === 0`, but an empty quoted string is a valid argument. For instance, `grep "" file.txt` would lose the empty pattern and become `["grep", "file.txt"]`, changing command semantics.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/sandbox-daytona/src/index.ts Outdated
let quote: "'" | '"' | null = null;
let escapeNext = false;

const pushCurrent = () => {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Empty quoted arguments (e.g., "" or '') are silently dropped by the tokenizer. pushCurrent skips tokens where current.length === 0, but an empty quoted string is a valid argument. For instance, grep "" file.txt would lose the empty pattern and become ["grep", "file.txt"], changing command semantics.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sandbox-e2b/src/index.ts, line 81:

<comment>Empty quoted arguments (e.g., `""` or `''`) are silently dropped by the tokenizer. `pushCurrent` skips tokens where `current.length === 0`, but an empty quoted string is a valid argument. For instance, `grep "" file.txt` would lose the empty pattern and become `["grep", "file.txt"]`, changing command semantics.</comment>

<file context>
@@ -72,6 +72,105 @@ const DEFAULT_TIMEOUT_MS = 60000;
+  let quote: "'" | '"' | null = null;
+  let escapeNext = false;
+
+  const pushCurrent = () => {
+    if (current.length > 0) {
+      tokens.push(current);
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@examples/with-workspace/workspace/skills/playwright-cli/references/request-mocking.md`:
- Around line 30-35: The fenced code block in request-mocking.md that contains
the patterns (e.g., "**/api/users - Exact path match", "**/api/*/details",
"**/*.{png,jpg,jpeg}", "**/search?q=*") is missing a language specifier; update
the opening fence from ``` to something like ```text (or ```plaintext) so the
block has a language identifier and the MD040 lint warning is resolved.

In
`@examples/with-workspace/workspace/skills/playwright-cli/references/storage-state.md`:
- Around line 243-266: Update the example filenames to match the .gitignore
pattern '*.auth-state.json': replace occurrences of "auth.json" with
"auth.auth-state.json" (or "auth-state.auth-state.json") and "my-session.json"
with "my-session.auth-state.json" in the code blocks (e.g., the commands using
playwright-cli state-save/state-load and the roundtrip example). Also update the
other instances noted (around the 271-273 example) so all sample state files end
with ".auth-state.json" to align with the security guidance.

In `@packages/sandbox-daytona/src/index.ts`:
- Around line 57-154: Export normalizeCommandAndArgs (and its helper
tokenizeCommandLine if needed) from `@voltagent/core` by adding them to the
sandbox package's public exports (e.g., export from
packages/core/src/workspace/sandbox/index.ts and re-export via the package's
main entry), promote `@voltagent/core` from devDependency to a runtime dependency,
then remove the duplicate implementations in
packages/sandbox-daytona/src/index.ts and import normalizeCommandAndArgs from
'@voltagent/core' (use the symbol normalizeCommandAndArgs to locate call sites
and tokenizeCommandLine if referenced).
🧹 Nitpick comments (6)
examples/with-workspace/workspace/skills/playwright-cli/references/tracing.md (2)

7-18: Consider using more descriptive selector examples.

The basic usage example uses generic selectors like e1 and e2 without context. While these may represent Playwright CLI's selector notation, adding brief comments or using more descriptive examples would improve clarity for users unfamiliar with the tool.

📝 Suggestion to improve example clarity
 ```bash
 # Start trace recording
 playwright-cli tracing-start
 
 # Perform actions
 playwright-cli open https://example.com
-playwright-cli click e1
-playwright-cli fill e2 "test"
+playwright-cli click e1  # Click the login button
+playwright-cli fill e2 "test"  # Fill the username field
 
 # Stop trace recording
 playwright-cli tracing-stop

</details>

---

`134-135`: **Consider a safer approach for the cleanup command.**

The `find -delete` command operates immediately without confirmation, which could be dangerous if the path is mistyped or if users adapt the command incorrectly. Consider adding a warning or showing a two-step approach (preview first, then delete).



<details>
<summary>🛡️ Safer alternative for cleanup command</summary>

```diff
-# Remove traces older than 7 days
-find .playwright-cli/traces -mtime +7 -delete
+# Preview traces older than 7 days before deleting
+find .playwright-cli/traces -mtime +7 -type f
+
+# After verifying, remove them
+find .playwright-cli/traces -mtime +7 -type f -delete

Or add a safety note:

 # Remove traces older than 7 days
+# ⚠️ Verify the path before running - this deletes files immediately
 find .playwright-cli/traces -mtime +7 -delete
packages/core/src/workspace/sandbox/local.ts (1)

503-504: Minor: redundant .trim() call.

normalizeCommandAndArgs already trims the command internally (line 81 in command-normalization.ts: command.trim()). The .trim() on line 504 is redundant. Not a bug, just a nit.

packages/sandbox-e2b/src/index.ts (2)

432-433: Redundant .trim()normalizeCommandAndArgs already trims the command.

normalizeCommandAndArgs trims the command internally (line 150), so the subsequent .trim() on line 433 is a no-op. Not a bug, but unnecessary.

🧹 Suggested simplification
     const normalized = normalizeCommandAndArgs(options.command ?? "", options.args);
-    const command = normalized.command.trim();
+    const command = normalized.command;

75-172: Duplicated command normalization logic — consider consolidating or exporting from @voltagent/core.

tokenizeCommandLine and normalizeCommandAndArgs are exact copies of the implementations in packages/core/src/workspace/sandbox/command-normalization.ts. These functions are not currently exported from the core package's public API, but consolidating them would eliminate maintenance burden. Options:

  1. Export normalizeCommandAndArgs and NormalizedCommand type from @voltagent/core, then import here.
  2. Create a shared internal utility if these should remain private to core.

Additionally, line 433's normalized.command.trim() is redundant—the command is already trimmed within normalizeCommandAndArgs.

packages/core/src/workspace/sandbox/toolkit.ts (1)

158-175: Type assertion to WorkspaceWithPathContext is safe but brittle.

The cast on line 165 assumes the workspace object may have a getPathContext method not declared in WorkspaceIdentity. This works because the optional chaining + try/catch (lines 166-174) make it safe at runtime, but it relies on duck-typing rather than the type system.

Consider extending WorkspaceIdentity (or using a union type in WorkspaceSandboxToolkitContext) to express that workspace may carry getPathContext, which would remove the need for the as cast.

Comment thread packages/sandbox-daytona/src/index.ts Outdated
Comment on lines +57 to +154
const tokenizeCommandLine = (value: string): string[] | null => {
const tokens: string[] = [];
let current = "";
let quote: "'" | '"' | null = null;
let escapeNext = false;

const pushCurrent = () => {
if (current.length > 0) {
tokens.push(current);
current = "";
}
};

for (const char of value) {
if (escapeNext) {
current += char;
escapeNext = false;
continue;
}

if (quote === null) {
if (char === "\\") {
escapeNext = true;
continue;
}
if (char === "'" || char === '"') {
quote = char;
continue;
}
if (/\s/.test(char)) {
pushCurrent();
continue;
}
current += char;
continue;
}

if (quote === "'") {
if (char === "'") {
quote = null;
} else {
current += char;
}
continue;
}

if (char === '"') {
quote = null;
continue;
}

if (char === "\\") {
escapeNext = true;
continue;
}

current += char;
}

if (escapeNext) {
current += "\\";
}

if (quote !== null) {
return null;
}

pushCurrent();
return tokens.length > 0 ? tokens : null;
};

const normalizeCommandAndArgs = (
command: string,
args?: string[],
): { command: string; args?: string[] } => {
const trimmedCommand = command.trim();
const normalizedArgs = args && args.length > 0 ? args : undefined;

if (!trimmedCommand) {
return { command: trimmedCommand, args: normalizedArgs };
}

if (!/\s/.test(trimmedCommand)) {
return { command: trimmedCommand, args: normalizedArgs };
}

const parsed = tokenizeCommandLine(trimmedCommand);
if (!parsed || parsed.length === 0) {
return { command: trimmedCommand, args: normalizedArgs };
}

const [normalizedCommand, ...parsedArgs] = parsed;
const mergedArgs = [...parsedArgs, ...(normalizedArgs ?? [])];
return {
command: normalizedCommand,
args: mergedArgs.length > 0 ? mergedArgs : undefined,
};
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the command-normalization.ts file in core
find . -path '*/node_modules' -prune -o -name 'command-normalization.ts' -type f -print

Repository: VoltAgent/voltagent

Length of output: 125


🏁 Script executed:

#!/bin/bash
# Check the structure of packages/core to understand exports
ls -la packages/core/src/ 2>/dev/null | head -20

Repository: VoltAgent/voltagent

Length of output: 1197


🏁 Script executed:

#!/bin/bash
# Look at packages/core's index or public entry points
find packages/core/src -name 'index.ts' -type f | head -5

Repository: VoltAgent/voltagent

Length of output: 253


🏁 Script executed:

#!/bin/bash
# Search for normalizeCommandAndArgs exports in core
rg -n 'export.*normalizeCommandAndArgs' --type=ts

Repository: VoltAgent/voltagent

Length of output: 224


🏁 Script executed:

#!/bin/bash
# Read the command-normalization.ts from core to compare
cat -n packages/core/src/workspace/sandbox/command-normalization.ts

Repository: VoltAgent/voltagent

Length of output: 2875


🏁 Script executed:

#!/bin/bash
# Read lines 57-154 from sandbox-daytona/src/index.ts
sed -n '57,154p' packages/sandbox-daytona/src/index.ts

Repository: VoltAgent/voltagent

Length of output: 2086


🏁 Script executed:

#!/bin/bash
# Check if command-normalization exports are in core's public index.ts
grep -n 'command-normalization' packages/core/src/index.ts

Repository: VoltAgent/voltagent

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check sandbox-daytona's package.json dependencies
cat packages/sandbox-daytona/package.json | grep -A 20 '"dependencies"'

Repository: VoltAgent/voltagent

Length of output: 503


🏁 Script executed:

#!/bin/bash
# Check the workspace/sandbox directory structure in core
find packages/core/src/workspace/sandbox -type f -name '*.ts' | head -20

Repository: VoltAgent/voltagent

Length of output: 407


🏁 Script executed:

#!/bin/bash
# Check what's exported from workspace/sandbox/index if it exists
cat packages/core/src/workspace/sandbox/index.ts 2>/dev/null || echo "No index.ts found"

Repository: VoltAgent/voltagent

Length of output: 604


🏁 Script executed:

#!/bin/bash
# Look at how other packages import from `@voltagent/core`
rg 'from.*@voltagent/core' --type=ts | grep -v node_modules | head -15

Repository: VoltAgent/voltagent

Length of output: 1510


🏁 Script executed:

#!/bin/bash
# Verify that `@voltagent/core` is indeed only in devDependencies for sandbox-daytona
cat packages/sandbox-daytona/package.json | jq '.dependencies, .devDependencies'

Repository: VoltAgent/voltagent

Length of output: 205


Remove duplication by exporting normalizeCommandAndArgs from @voltagent/core's public API, then import it in sandbox-daytona.

The tokenizeCommandLine and normalizeCommandAndArgs functions in lines 57-154 are identical to those in packages/core/src/workspace/sandbox/command-normalization.ts. Currently, these functions are not re-exported from @voltagent/core's public API, making it impossible to import them directly. To enable this refactor, they must first be added to the exports in packages/core/src/workspace/sandbox/index.ts and made available through the package's public entry point. Once exported, update sandbox-daytona to import from @voltagent/core instead of maintaining a duplicate copy. Note: @voltagent/core is currently only a devDependency; it will need to be promoted to a runtime dependency.

🤖 Prompt for AI Agents
In `@packages/sandbox-daytona/src/index.ts` around lines 57 - 154, Export
normalizeCommandAndArgs (and its helper tokenizeCommandLine if needed) from
`@voltagent/core` by adding them to the sandbox package's public exports (e.g.,
export from packages/core/src/workspace/sandbox/index.ts and re-export via the
package's main entry), promote `@voltagent/core` from devDependency to a runtime
dependency, then remove the duplicate implementations in
packages/sandbox-daytona/src/index.ts and import normalizeCommandAndArgs from
'@voltagent/core' (use the symbol normalizeCommandAndArgs to locate call sites
and tokenizeCommandLine if referenced).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/core/src/agent/agent.ts`:
- Around line 369-388: The resolveWorkspaceSkillsPromptHook function skips
injecting the default workspace skills prompt whenever
options.hooks?.onPrepareMessages exists; change it so the default prompt is only
disabled when options.workspaceSkillsPrompt === false. Remove or modify the
early return that checks options.hooks?.onPrepareMessages and instead return a
hook (from resolveWorkspaceSkillsPromptHook) that composes with an existing
onPrepareMessages: call the existing hook if present and then inject or merge
the workspace skills prompt (respecting promptConfig and
isWorkspaceSkillsToolkitEnabled) so custom hooks and the default skills prompt
both run unless workspaceSkillsPrompt is explicitly false.
🧹 Nitpick comments (2)
packages/core/src/workspace/sandbox/toolkit.ts (2)

158-175: The as cast is guarded but could benefit from a narrowing type guard.

Line 165 uses as WorkspaceWithPathContext which bypasses type checking. While the runtime guard on line 166 (?.getPathContext) makes this safe in practice, a type predicate would be more idiomatic TypeScript and catch regressions at compile time.

♻️ Optional: extract a type guard
+const hasPathContext = (
+  workspace: WorkspaceIdentity | undefined,
+): workspace is WorkspaceWithPathContext =>
+  typeof (workspace as WorkspaceWithPathContext)?.getPathContext === "function";
+
 const resolvePathContext = (
   context: WorkspaceSandboxToolkitContext,
 ): WorkspacePathContext | null => {
   if (context.pathContext) {
     return context.pathContext;
   }
 
-  const workspaceWithPathContext = context.workspace as WorkspaceWithPathContext | undefined;
-  if (!workspaceWithPathContext?.getPathContext) {
+  if (!hasPathContext(context.workspace)) {
     return null;
   }
 
   try {
-    return workspaceWithPathContext.getPathContext() ?? null;
+    return context.workspace.getPathContext() ?? null;
   } catch {
     return null;
   }
 };

265-271: Null-handling semantics differ between systemPrompt and customToolDescription.

systemPrompt uses strict === undefined (line 271), letting null pass through to explicitly disable the prompt. customToolDescription uses || (line 303), which collapses both null and "" to the default. This appears intentional (disabling the tool description has no practical use), but documenting this distinction in WorkspaceSandboxToolkitOptions would prevent future confusion.

Also applies to: 301-303

Comment on lines +369 to +388
const resolveWorkspaceSkillsPromptHook = (
workspace: Workspace | undefined,
options: AgentOptions,
): AgentHooks["onPrepareMessages"] | undefined => {
if (!workspace?.skills) {
return undefined;
}

const promptConfig = options.workspaceSkillsPrompt;
if (promptConfig === false) {
return undefined;
}

const hasExplicitPromptConfig = promptConfig !== undefined;
if (!hasExplicitPromptConfig && options.hooks?.onPrepareMessages) {
return undefined;
}
if (!hasExplicitPromptConfig && !isWorkspaceSkillsToolkitEnabled(options.workspaceToolkits)) {
return undefined;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Default workspace skills prompt is skipped when a custom onPrepareMessages hook is present.

Line 383-385 short-circuits default injection whenever options.hooks?.onPrepareMessages is set. That contradicts the “auto‑inject by default” objective and means agents with custom hooks won’t get skills guidance unless they explicitly opt in. Consider letting the composition handle ordering and only disable via workspaceSkillsPrompt === false.

💡 Suggested fix
-  if (!hasExplicitPromptConfig && options.hooks?.onPrepareMessages) {
-    return undefined;
-  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resolveWorkspaceSkillsPromptHook = (
workspace: Workspace | undefined,
options: AgentOptions,
): AgentHooks["onPrepareMessages"] | undefined => {
if (!workspace?.skills) {
return undefined;
}
const promptConfig = options.workspaceSkillsPrompt;
if (promptConfig === false) {
return undefined;
}
const hasExplicitPromptConfig = promptConfig !== undefined;
if (!hasExplicitPromptConfig && options.hooks?.onPrepareMessages) {
return undefined;
}
if (!hasExplicitPromptConfig && !isWorkspaceSkillsToolkitEnabled(options.workspaceToolkits)) {
return undefined;
}
const resolveWorkspaceSkillsPromptHook = (
workspace: Workspace | undefined,
options: AgentOptions,
): AgentHooks["onPrepareMessages"] | undefined => {
if (!workspace?.skills) {
return undefined;
}
const promptConfig = options.workspaceSkillsPrompt;
if (promptConfig === false) {
return undefined;
}
const hasExplicitPromptConfig = promptConfig !== undefined;
if (!hasExplicitPromptConfig && !isWorkspaceSkillsToolkitEnabled(options.workspaceToolkits)) {
return undefined;
}
🤖 Prompt for AI Agents
In `@packages/core/src/agent/agent.ts` around lines 369 - 388, The
resolveWorkspaceSkillsPromptHook function skips injecting the default workspace
skills prompt whenever options.hooks?.onPrepareMessages exists; change it so the
default prompt is only disabled when options.workspaceSkillsPrompt === false.
Remove or modify the early return that checks options.hooks?.onPrepareMessages
and instead return a hook (from resolveWorkspaceSkillsPromptHook) that composes
with an existing onPrepareMessages: call the existing hook if present and then
inject or merge the workspace skills prompt (respecting promptConfig and
isWorkspaceSkillsToolkitEnabled) so custom hooks and the default skills prompt
both run unless workspaceSkillsPrompt is explicitly false.

@omeraplak omeraplak merged commit b0482cb into main Feb 13, 2026
22 checks passed
@omeraplak omeraplak deleted the fix/workspace-skill branch February 13, 2026 15:31
@coderabbitai coderabbitai Bot mentioned this pull request Apr 1, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] System prompt incorrectly injects full skill instructions and lacks skill access guidance

1 participant