feat: use OpenClaw sub-agent for slow detect commands, fix keyword generation
register-skill-release / register (push) Failing after 1m36s Details

- SKILL.md: detect/detect-and-search now spawned via sessions_spawn (non-blocking); search/session run inline
- product-detector.ts: replace sequential chunk loop with worker-pool concurrency (withConcurrency) so all frames dispatch immediately up to the limit
- index.ts: fix generateChineseKeyword prompt to name the container/organizer object, not the items it holds (e.g. 鞋架 not 鞋)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ywkj 2026-04-21 08:20:37 +08:00
parent 9f381f9bab
commit 778a51ad45
3 changed files with 51 additions and 17 deletions

View File

@ -86,9 +86,29 @@ VISION_API_BASE=http://localhost:11434/v1
VISION_MODEL=llava:13b
```
## Rules — MUST follow
## Execution rules
1. **Execute only, do not reason about internals.** Run the CLI and return the output.
2. **No fallback strategies.** Report errors as-is; do NOT try alternative approaches.
3. **No retry loops.** If detection or search fails, report the failure.
4. **Trust the tool's output.** The CLI handles session management and error formatting internally.
### For `detect` and `detect-and-search` (slow — use sub-agent)
Spawn a sub-agent via `sessions_spawn`. Do **not** run the command directly.
```
sessions_spawn(
task: "Run this command and return the raw JSON output:\n\nbun dist/run.js <full command here>\n\nCopy the entire JSON output as your reply.",
label: "video-product-snapshot",
runTimeoutSeconds: 300,
)
```
- Announce immediately that processing has started and share the `runId`.
- Wait for the sub-agent announcement, then parse and format the result for the user.
### For `search` and `session` (fast — run directly)
Run the CLI command inline, no sub-agent needed.
### General rules
1. **No fallback strategies.** Report errors as-is; do NOT try alternative approaches.
2. **No retry loops.** If detection or search fails, report the failure.
3. **Trust the tool's output.** The CLI handles session management and error formatting internally.

View File

@ -198,10 +198,12 @@ async function generateChineseKeyword(description: string, visionConfig: VisionC
model,
prompt: `You are generating a 1688.com (Chinese B2B wholesale) product search keyword.
Rules:
- Output ONLY 2-4 Chinese words the product category + 1-2 key material/feature words
- Output ONLY 2-4 Chinese words the product OBJECT TYPE + 1-2 key material/feature words
- CRITICAL: If the product is a container, organizer, rack, shelf, bag, box, or holder, the keyword MUST name THAT object NOT the items it holds.
Examples: shoe rack "金属鞋架", cable organizer "理线器", storage shelf "收纳架", toolbox "工具箱"
- Use common Chinese commerce terms, NOT a literal translation
- No English, no punctuation, no explanation
- Short broad terms work better than long specific phrases (e.g. "金属鞋架" not "黑色Z型金属网格鞋架")
- Short broad terms work better than long specific phrases
Product description: ${description}

View File

@ -135,23 +135,35 @@ export async function cropProduct(
return outputPath;
}
async function withConcurrency<T>(
tasks: (() => Promise<T>)[],
limit: number,
): Promise<T[]> {
const results: T[] = new Array(tasks.length);
let next = 0;
async function worker() {
while (next < tasks.length) {
const i = next++;
results[i] = await tasks[i]();
}
}
await Promise.all(Array.from({ length: Math.min(limit, tasks.length) }, worker));
return results;
}
export async function detectProductFrames(
frames: ExtractedFrame[],
minConfidence: number,
concurrency: number = 5,
concurrency: number = 10,
visionConfig: VisionConfig,
): Promise<ProductFrame[]> {
const model = createVisionModel(visionConfig);
// Pass 1: parallel filter — discard junk frames
const keepFlags: boolean[] = [];
for (let i = 0; i < frames.length; i += concurrency) {
const chunk = frames.slice(i, i + concurrency);
const flags = await Promise.all(
chunk.map((f) => filterFrame(f, model).catch(() => false))
// Pass 1: all frames in parallel, bounded by concurrency
const keepFlags = await withConcurrency(
frames.map((f) => () => filterFrame(f, model).catch(() => false)),
concurrency,
);
keepFlags.push(...flags);
}
const candidates = frames.filter((_, i) => keepFlags[i]);
if (candidates.length === 0) return [];