Kaynağa Gözat

test(fs-search): minimize glob snapshot composition

Boot the glob sampling scenario from a standalone ACP composition that exposes only bash, glob, and grep. Regenerate the smaller header fixtures and trim implementation narration already owned by the Agent Note.
NI0317 1 ay önce
ebeveyn
işleme
6b79ce08c5

+ 2 - 2
.agents/notes/implemented/bug-fix/2026-07-27-glob-sampling.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-27-glob-sampling.md
-2026-07-27-glob-sampling.md: c583f0cd8110684a94d0cd04f5cf2ae861ce7aa3
-2026-07-27-glob-sampling.zh.md: 97025a7714237fc716dee745a858140cd783ba2d
+2026-07-27-glob-sampling.md: b9dfc7fc89ff61824094ccd5297f766c665f4edb
+2026-07-27-glob-sampling.zh.md: 4f022467e461247c746e906b32f5ecffcb885d56

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-07-27-glob-sampling.md

@@ -42,4 +42,4 @@ The tool surface does not grow. The fix changes glob's prompt, schema descriptio
 
 ## Testing
 
-Package tests pin concentrated and flat results, explicit roots, more groups than the JavaScript argument limit, exhausted groups, fewer slots than groups, and paths outside the workdir. The `fs-glob-sampling` ACP scenario boots the real Loader/app/sandbox-bash composition and executes the real search plugin against a deterministic `rg` process fixture; its result spans four top-level entries instead of returning one subtree's head.
+Package tests pin concentrated and flat results, explicit roots, more groups than the JavaScript argument limit, exhausted groups, fewer slots than groups, and paths outside the workdir. The `fs-glob-sampling` ACP scenario boots a minimal real Loader/app/local-bash composition and executes the real search plugin against a deterministic `rg` process fixture; its result spans four top-level entries instead of returning one subtree's head.

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-07-27-glob-sampling.zh.md

@@ -42,4 +42,4 @@ footer 会说明当前页面是跨条目的样本,而不是按修改时间排
 
 ## 测试
 
-包测试锁定了结果集中与扁平两种情况、显式根目录、分组数超过 JavaScript 参数个数上限、分组耗尽、位置数少于分组数,以及工作目录以外的路径。`fs-glob-sampling` ACP(Agent Client Protocol)场景会启动真实的 Loader/app/sandbox-bash 组合,并让真实搜索插件对接确定性的 `rg` 进程 fixture(测试前置数据);其结果覆盖 4 个顶层条目,而不是只返回某棵子树的前部。
+包测试锁定了结果集中与扁平两种情况、显式根目录、分组数超过 JavaScript 参数个数上限、分组耗尽、位置数少于分组数,以及工作目录以外的路径。`fs-glob-sampling` ACP(Agent Client Protocol)场景会启动最小化的真实 Loader/app/local-bash 组合,并让真实搜索插件对接确定性的 `rg` 进程 fixture(测试前置数据);其结果覆盖 4 个顶层条目,而不是只返回某棵子树的前部。

+ 0 - 24
examples/acp-agent/fs-search.cordis.snapshot.yml

@@ -1,24 +0,0 @@
-# Keyless counterpart to fs-search.cordis.yml: the search plugin and sandboxed
-# bash execution remain real; only the model adapter is replaced by replay.
-- id: base
-  name: '@cordisjs/plugin-include'
-  config:
-    path: ./cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - insert:
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash
-                    - id: deepseek-v4-pro
-          - id: tool-fs-search
-            name: '@deepseek-ai/dsh-tool-fs-search'
-            config:
-              globMaxResults: 4

+ 0 - 13
examples/acp-agent/fs-search.cordis.yml

@@ -1,13 +0,0 @@
-# Search snapshot composition: mount the real model-facing search plugin over
-# the shipped sandboxed bash stack. A four-path inline cap keeps the fixture
-# small while forcing the over-cap sampling branch.
-- id: base
-  name: '@cordisjs/plugin-include'
-  config:
-    path: ./cordis.yml
-    patches:
-      - insert:
-          - id: tool-fs-search
-            name: '@deepseek-ai/dsh-tool-fs-search'
-            config:
-              globMaxResults: 4

+ 1 - 1
examples/acp-agent/tests/acp.snapshot.ts

@@ -42,7 +42,7 @@ const RETRY_CONFIG = fileURLToPath(new URL('../retry.cordis.yml', import.meta.ur
 const SESSION_TITLE_CONFIG = fileURLToPath(new URL('../session-title.cordis.yml', import.meta.url))
 const LSP_CONFIG = fileURLToPath(new URL('./lsp.cordis.yml', import.meta.url))
 const WEB_CONFIG = fileURLToPath(new URL('../web.cordis.yml', import.meta.url))
-const FS_SEARCH_CONFIG = fileURLToPath(new URL('../fs-search.cordis.yml', import.meta.url))
+const FS_SEARCH_CONFIG = fileURLToPath(new URL('./fs-search.cordis.yml', import.meta.url))
 const FS_SEARCH_BIN = fileURLToPath(new URL('./fixtures/fs-search-bin', import.meta.url))
 const SNAPSHOTS_DIR = join(dirname(fileURLToPath(import.meta.url)), 'snapshots')
 const PACKED_CHUNKS_SOURCE = 'hook-cc-pretool-deny'

+ 34 - 0
examples/acp-agent/tests/fs-search.cordis.snapshot.yml

@@ -0,0 +1,34 @@
+# Minimal keyless composition: real app, bash, and search tool; replayed model.
+- id: llm-replay
+  name: '@deepseek-ai/dsh-llm-replay'
+  config:
+    providers:
+      - id: deepseek
+        name: DeepSeek
+        models:
+          - id: deepseek-v4-pro
+
+- id: subprocess
+  name: '@deepseek-ai/dsh-subprocess-local'
+
+- id: bash
+  name: '@deepseek-ai/dsh-bash-local'
+
+- id: acp-agent
+  name: '@deepseek-ai/dsh-acp-demo'
+  config:
+    provider: deepseek
+    model: deepseek-v4-pro
+    persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    persistenceCompression: none
+    workspaceContext: false
+    skills:
+      enabled: false
+    toolTasks: false
+    goals: false
+    persona: You are a concise snapshot agent working in {{cwd}}.
+
+- id: tool-fs-search
+  name: '@deepseek-ai/dsh-tool-fs-search'
+  config:
+    globMaxResults: 4

+ 32 - 0
examples/acp-agent/tests/fs-search.cordis.yml

@@ -0,0 +1,32 @@
+# Minimal live counterpart for the glob-sampling snapshot composition.
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  config:
+    apiKey: !!js process.env.DEEPSEEK_API_KEY
+    baseURL: !!js process.env.DEEPSEEK_BASE_URL
+    models:
+      - id: deepseek-v4-pro
+
+- id: subprocess
+  name: '@deepseek-ai/dsh-subprocess-local'
+
+- id: bash
+  name: '@deepseek-ai/dsh-bash-local'
+
+- id: acp-agent
+  name: '@deepseek-ai/dsh-acp-demo'
+  config:
+    provider: deepseek
+    model: deepseek-v4-pro
+    persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    workspaceContext: false
+    skills:
+      enabled: false
+    toolTasks: false
+    goals: false
+    persona: You are a concise snapshot agent working in {{cwd}}.
+
+- id: tool-fs-search
+  name: '@deepseek-ai/dsh-tool-fs-search'
+  config:
+    globMaxResults: 4

+ 1 - 21
examples/acp-agent/tests/snapshots/fs-glob-sampling/system-prompt.expected.md

@@ -1,29 +1,9 @@
 You are an AI agent powered by the DeepSeek Harness SDK.
 
-You are a coding assistant powered by the deepseek-v4-pro model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
-
-Verify your work by running the code or tests. Keep answers brief and factual.
-
-
-Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
-
-Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
-
-Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
+You are a concise snapshot agent working in {{cwd}}.
 
 Use the glob tool — not shell find — to discover files by path pattern. A pattern with no "/" matches basenames at any depth, so "*" matches every file in the tree rather than its top level. Results are files only, never directories, and include hidden and ignored files: a result that fits comes back in modification-time order, while a larger one is sampled across top-level entries, so it spans the tree instead of one subtree.
 
 Use the grep tool — not shell grep or rg — to search file contents. Use read on a matched file when you need surrounding context.
 
 Check the [exit code: N] marker on every bash result; investigate failures before moving on.
-
-Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.
-
-Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked.
-
-Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).
-<!-- dsh-user-approval-policy:never -->
-
-Use the workflow tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.
-
-Use the ralph tool ONLY when the direct human explicitly asks for a Ralph loop or fresh-agent iterative execution. Each Ralph round starts a fresh child with no conversation seed and uses the shared workspace as durable memory. Completion and blockers are worker reports, not independent evaluation. Use same-session goal tools for ordinary long-running objectives, and plain subagents or workflows for bounded delegation and fan-out.

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 332
examples/acp-agent/tests/snapshots/fs-glob-sampling/tool-schemas.expected.json


+ 5 - 30
packages/fs/tool-fs-search/src/glob.ts

@@ -5,13 +5,6 @@
  * model-facing schema, argument validation, shell-safe command construction,
  * result parsing, inline sampling, and formatting; process concerns (defaulting,
  * scrubbing, kill, backend substitution) stay behind `ctx.bash`.
- *
- * A complete result keeps ripgrep's modification-time order. A result too large
- * to show inline does NOT: its inline page is sampled across the complete
- * result's top-level entries ({@link sampleAcrossTopLevel}), because the sorted
- * head of a broad match is routinely one subtree's worth of files and reads as
- * if the workspace held nothing else.
- *
  * @module @deepseek-ai/dsh-tool-fs-search/glob
  */
 
@@ -145,19 +138,9 @@ function topLevelSegment(path: string): string {
  * Choose the inline page of an over-cap result by round-robin across the
  * complete result's top-level entries, instead of taking its head.
  *
- * `--sort=modified` (oldest first) is the right order for a complete result and
- * the wrong basis for a sample of one: a broad pattern in a workspace holding one
- * unpacked archive — whose restored timestamps predate everything the user
- * wrote — gives a head that is entirely that subtree, and the model reads the
- * page as the workspace. Round-robin gives every top-level entry a slot before
- * any entry gets a second, so the page spans the tree; an entry that runs out of
- * paths drops out and its remaining slots go to the rest.
- *
- * Modification-time order survives where it still means something: groups are
- * visited in the order ripgrep first emits them, and each group's own paths keep
- * their relative order. With one path per group — a flat result — this
- * reproduces the sorted head exactly, so nothing changes for a result that has
- * no subtree to hide.
+ * Every top-level entry receives a slot before any receives a second; exhausted
+ * groups drop out. Group order and order within each group follow `paths`, so a
+ * flat result reproduces the modification-time head.
  *
  * @param paths - the complete result, in ripgrep's modification-time order.
  * @param maxItems - how many paths the page may hold; the caller has already established it is smaller than `paths`.
@@ -191,16 +174,8 @@ export function sampleAcrossTopLevel(paths: readonly string[], maxItems: number,
 }
 
 /**
- * Format a CAPPED `glob` result: the inline page, then a footer stating that
- * the page is a cross-directory sample rather than the most recent paths, how
- * much of the top level it reaches, and either the formatted-spill recovery
- * locator or the could-not-save explanation. The omitted count is a budget
- * fact: the search itself completed. A result that fits inline never reaches
- * here — it is emitted verbatim, in ripgrep's order.
- *
- * A result whose every path is its own top-level entry keeps the plain footer:
- * the sample is the modification-time-ordered head, and naming a spread would only
- * restate the path counts already there.
+ * Format a capped sampled page and its complete-result recovery path. A flat
+ * result keeps the plain footer because its sample is the modification-time head.
  *
  * @param sample - the inline page and its top-level spread.
  * @param seen - how many paths the complete result holds; always more than the page.

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor