qlogicagent 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.js +1 -0
- package/dist/cli.js +9 -0
- package/dist/contracts.js +1 -0
- package/dist/index.js +5 -15
- package/dist/orchestration.js +118 -0
- package/package.json +56 -42
- package/dist/agent/agent.js +0 -113
- package/dist/agent/tool-loop.js +0 -575
- package/dist/agent/types.js +0 -14
- package/dist/cli/main.js +0 -23
- package/dist/cli/stdio-server.js +0 -463
- package/dist/config/config.js +0 -21
- package/dist/contracts/hooks.js +0 -7
- package/dist/contracts/index.js +0 -10
- package/dist/contracts/planner.js +0 -2
- package/dist/contracts/skill-candidate.js +0 -195
- package/dist/contracts/todo.js +0 -9
- package/dist/llm/builtin-providers.js +0 -531
- package/dist/llm/index.js +0 -14
- package/dist/llm/llm-client.js +0 -67
- package/dist/llm/model-catalog.js +0 -191
- package/dist/llm/provider-def.js +0 -12
- package/dist/llm/provider-registry.js +0 -147
- package/dist/llm/transport.js +0 -27
- package/dist/llm/transports/anthropic-messages.js +0 -293
- package/dist/llm/transports/openai-chat.js +0 -165
- package/dist/orchestration/agent-registry.js +0 -116
- package/dist/orchestration/approval-aware-tool-plan.js +0 -87
- package/dist/orchestration/context-compression.js +0 -583
- package/dist/orchestration/conversation-repair.js +0 -429
- package/dist/orchestration/curator-scheduler.js +0 -135
- package/dist/orchestration/embedded-failover-policy.js +0 -168
- package/dist/orchestration/error-classification.js +0 -77
- package/dist/orchestration/failover-classification.js +0 -381
- package/dist/orchestration/failover-error.js +0 -198
- package/dist/orchestration/fork-subagent.js +0 -98
- package/dist/orchestration/index.js +0 -267
- package/dist/orchestration/memory-flush-policy.js +0 -85
- package/dist/orchestration/memory-provider.js +0 -2
- package/dist/orchestration/parallel-tool-calls.js +0 -59
- package/dist/orchestration/prompt-cache-strategy.js +0 -228
- package/dist/orchestration/reactive-compact.js +0 -78
- package/dist/orchestration/retry-loop.js +0 -24
- package/dist/orchestration/skill-candidate.js +0 -141
- package/dist/orchestration/skill-consolidation.js +0 -220
- package/dist/orchestration/skill-improvement.js +0 -66
- package/dist/orchestration/skill-similarity.js +0 -131
- package/dist/orchestration/streaming-tool-executor.js +0 -96
- package/dist/orchestration/team-orchestration.js +0 -369
- package/dist/orchestration/team-tool-loop-wiring.js +0 -147
- package/dist/orchestration/tool-choice-policy.js +0 -164
- package/dist/orchestration/tool-loop-state.js +0 -133
- package/dist/orchestration/tool-schema.js +0 -297
- package/dist/orchestration/transcript-repair.js +0 -426
- package/dist/orchestration/turn-loop-guard.js +0 -92
- package/dist/orchestration/web-browser-policy.js +0 -39
- package/dist/runtime/context-compression.js +0 -274
- package/dist/runtime/hook-registry.js +0 -53
- package/dist/runtime/memory-hooks.js +0 -65
- package/dist/runtime/tool-eligibility.js +0 -111
- package/dist/skills/index.js +0 -82
- package/dist/skills/memory-extractor.js +0 -173
- package/dist/skills/memory-query-tool.js +0 -127
- package/dist/skills/memory-store.js +0 -228
- package/dist/skills/memory-tool.js +0 -192
- package/dist/skills/portable-tool.js +0 -14
- package/dist/skills/qmemory-adapter.js +0 -165
- package/dist/skills/skill-frontmatter.js +0 -344
- package/dist/skills/skill-guard.js +0 -229
- package/dist/skills/skill-loader.js +0 -303
- package/dist/skills/skill-source.js +0 -126
- package/dist/skills/skill-types.js +0 -6
- package/dist/skills/think-tool.js +0 -59
- package/dist/skills/todo-tool.js +0 -114
- package/dist/skills/tools/agent-tool.js +0 -142
- package/dist/skills/tools/apply-patch-tool.js +0 -184
- package/dist/skills/tools/ask-user-tool.js +0 -121
- package/dist/skills/tools/brief-tool.js +0 -95
- package/dist/skills/tools/browser-tool.js +0 -155
- package/dist/skills/tools/checkpoint-tool.js +0 -102
- package/dist/skills/tools/config-tool.js +0 -143
- package/dist/skills/tools/cron-tool.js +0 -175
- package/dist/skills/tools/edit-tool.js +0 -70
- package/dist/skills/tools/exec-tool.js +0 -133
- package/dist/skills/tools/image-generate-tool.js +0 -67
- package/dist/skills/tools/instructions-tool.js +0 -187
- package/dist/skills/tools/lsp-tool.js +0 -227
- package/dist/skills/tools/mcp-client-types.js +0 -53
- package/dist/skills/tools/mcp-tool.js +0 -503
- package/dist/skills/tools/memory-tool.js +0 -88
- package/dist/skills/tools/monitor-tool.js +0 -131
- package/dist/skills/tools/music-generate-tool.js +0 -62
- package/dist/skills/tools/notify-tool.js +0 -62
- package/dist/skills/tools/patch-tool.js +0 -505
- package/dist/skills/tools/pdf-tool.js +0 -88
- package/dist/skills/tools/plan-mode-tool.js +0 -122
- package/dist/skills/tools/read-tool.js +0 -84
- package/dist/skills/tools/repl-tool.js +0 -69
- package/dist/skills/tools/search-tool.js +0 -225
- package/dist/skills/tools/send-message-tool.js +0 -76
- package/dist/skills/tools/skill-list-tool.js +0 -54
- package/dist/skills/tools/skill-manage-tool.js +0 -153
- package/dist/skills/tools/skill-view-tool.js +0 -72
- package/dist/skills/tools/sleep-tool.js +0 -81
- package/dist/skills/tools/structured-output-tool.js +0 -176
- package/dist/skills/tools/task-tool.js +0 -161
- package/dist/skills/tools/team-tool.js +0 -105
- package/dist/skills/tools/tool-search-tool.js +0 -110
- package/dist/skills/tools/tts-tool.js +0 -45
- package/dist/skills/tools/video-edit-tool.js +0 -74
- package/dist/skills/tools/video-generate-tool.js +0 -66
- package/dist/skills/tools/video-merge-tool.js +0 -92
- package/dist/skills/tools/video-upscale-tool.js +0 -52
- package/dist/skills/tools/web-fetch-tool.js +0 -92
- package/dist/skills/tools/web-search-tool.js +0 -86
- package/dist/skills/tools/worktree-tool.js +0 -147
- package/dist/skills/tools/write-tool.js +0 -81
- /package/dist/{agent → types/agent}/agent.d.ts +0 -0
- /package/dist/{agent → types/agent}/tool-loop.d.ts +0 -0
- /package/dist/{agent → types/agent}/types.d.ts +0 -0
- /package/dist/{cli → types/cli}/main.d.ts +0 -0
- /package/dist/{cli → types/cli}/stdio-server.d.ts +0 -0
- /package/dist/{config → types/config}/config.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/hooks.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/index.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/planner.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/skill-candidate.d.ts +0 -0
- /package/dist/{contracts → types/contracts}/todo.d.ts +0 -0
- /package/dist/{index.d.ts → types/index.d.ts} +0 -0
- /package/dist/{llm → types/llm}/builtin-providers.d.ts +0 -0
- /package/dist/{llm → types/llm}/index.d.ts +0 -0
- /package/dist/{llm → types/llm}/llm-client.d.ts +0 -0
- /package/dist/{llm → types/llm}/model-catalog.d.ts +0 -0
- /package/dist/{llm → types/llm}/provider-def.d.ts +0 -0
- /package/dist/{llm → types/llm}/provider-registry.d.ts +0 -0
- /package/dist/{llm → types/llm}/transport.d.ts +0 -0
- /package/dist/{llm → types/llm}/transports/anthropic-messages.d.ts +0 -0
- /package/dist/{llm → types/llm}/transports/openai-chat.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/agent-registry.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/approval-aware-tool-plan.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/context-compression.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/conversation-repair.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/curator-scheduler.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/embedded-failover-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/error-classification.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/failover-classification.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/failover-error.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/fork-subagent.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/index.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/memory-flush-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/memory-provider.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/parallel-tool-calls.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/prompt-cache-strategy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/reactive-compact.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/retry-loop.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-candidate.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-consolidation.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-improvement.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/skill-similarity.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/streaming-tool-executor.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/team-orchestration.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/team-tool-loop-wiring.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-choice-policy.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-loop-state.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/tool-schema.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/transcript-repair.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/turn-loop-guard.d.ts +0 -0
- /package/dist/{orchestration → types/orchestration}/web-browser-policy.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/context-compression.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/hook-registry.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/memory-hooks.d.ts +0 -0
- /package/dist/{runtime → types/runtime}/tool-eligibility.d.ts +0 -0
- /package/dist/{skills → types/skills}/index.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-extractor.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-query-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-store.d.ts +0 -0
- /package/dist/{skills → types/skills}/memory-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/portable-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/qmemory-adapter.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-frontmatter.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-guard.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-loader.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-source.d.ts +0 -0
- /package/dist/{skills → types/skills}/skill-types.d.ts +0 -0
- /package/dist/{skills → types/skills}/think-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/todo-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/agent-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/apply-patch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/ask-user-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/brief-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/browser-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/checkpoint-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/config-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/cron-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/edit-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/exec-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/image-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/instructions-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/lsp-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/mcp-client-types.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/mcp-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/memory-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/monitor-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/music-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/notify-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/patch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/pdf-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/plan-mode-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/read-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/repl-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/send-message-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-list-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-manage-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/skill-view-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/sleep-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/structured-output-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/task-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/team-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/tool-search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/tts-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-edit-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-generate-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-merge-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/video-upscale-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/web-fetch-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/web-search-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/worktree-tool.d.ts +0 -0
- /package/dist/{skills → types/skills}/tools/write-tool.d.ts +0 -0
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Skill discovery & loading — portable, DI-based.
|
|
3
|
-
// Mirrors openclaw/src/agents/skills/workspace.ts logic but decoupled from
|
|
4
|
-
// Node.js fs/path via SkillLoaderDeps.
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
import { parseFrontmatter, resolveSkillMetadata, resolveSkillInvocationPolicy } from "./skill-frontmatter.js";
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// Default limits (match openclaw upstream)
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
const DEFAULT_LIMITS = {
|
|
11
|
-
maxCandidatesPerRoot: 300,
|
|
12
|
-
maxSkillsLoadedPerSource: 200,
|
|
13
|
-
maxSkillsInPrompt: 150,
|
|
14
|
-
maxSkillsPromptChars: 30_000,
|
|
15
|
-
maxSkillFileBytes: 256_000,
|
|
16
|
-
};
|
|
17
|
-
function resolveLimits(input) {
|
|
18
|
-
return {
|
|
19
|
-
maxCandidatesPerRoot: input?.maxCandidatesPerRoot ?? DEFAULT_LIMITS.maxCandidatesPerRoot,
|
|
20
|
-
maxSkillsLoadedPerSource: input?.maxSkillsLoadedPerSource ?? DEFAULT_LIMITS.maxSkillsLoadedPerSource,
|
|
21
|
-
maxSkillsInPrompt: input?.maxSkillsInPrompt ?? DEFAULT_LIMITS.maxSkillsInPrompt,
|
|
22
|
-
maxSkillsPromptChars: input?.maxSkillsPromptChars ?? DEFAULT_LIMITS.maxSkillsPromptChars,
|
|
23
|
-
maxSkillFileBytes: input?.maxSkillFileBytes ?? DEFAULT_LIMITS.maxSkillFileBytes,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// Path containment guard
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
function isPathInside(parentRealPath, childRealPath, sep) {
|
|
30
|
-
const normalizedParent = parentRealPath.endsWith(sep) ? parentRealPath : parentRealPath + sep;
|
|
31
|
-
return childRealPath === parentRealPath || childRealPath.startsWith(normalizedParent);
|
|
32
|
-
}
|
|
33
|
-
function resolveContainedPath(deps, rootRealPath, candidatePath) {
|
|
34
|
-
try {
|
|
35
|
-
const real = deps.fs.realpathSync(candidatePath);
|
|
36
|
-
return isPathInside(rootRealPath, real, deps.path.sep) ? real : null;
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
// Directory listing helpers
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
function listChildDirectories(deps, dir) {
|
|
46
|
-
try {
|
|
47
|
-
const entries = deps.fs.readdirSync(dir, { withFileTypes: true });
|
|
48
|
-
const dirs = [];
|
|
49
|
-
for (const entry of entries) {
|
|
50
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
51
|
-
continue;
|
|
52
|
-
if (entry.isDirectory()) {
|
|
53
|
-
dirs.push(entry.name);
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (entry.isSymbolicLink()) {
|
|
57
|
-
try {
|
|
58
|
-
const fullPath = deps.path.join(dir, entry.name);
|
|
59
|
-
if (deps.fs.statSync(fullPath).isDirectory()) {
|
|
60
|
-
dirs.push(entry.name);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
// broken symlink — skip
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return dirs;
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Detect nested `skills/` subfolder. If `dir/skills/<x>/SKILL.md` exists,
|
|
76
|
-
* treat `dir/skills/` as the real root.
|
|
77
|
-
*/
|
|
78
|
-
function resolveNestedSkillsRoot(deps, dir, maxEntriesToScan) {
|
|
79
|
-
const nested = deps.path.join(dir, "skills");
|
|
80
|
-
try {
|
|
81
|
-
if (!deps.fs.existsSync(nested) || !deps.fs.statSync(nested).isDirectory()) {
|
|
82
|
-
return dir;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
return dir;
|
|
87
|
-
}
|
|
88
|
-
const nestedDirs = listChildDirectories(deps, nested);
|
|
89
|
-
const toScan = nestedDirs.slice(0, Math.min(nestedDirs.length, maxEntriesToScan));
|
|
90
|
-
for (const name of toScan) {
|
|
91
|
-
const skillMd = deps.path.join(nested, name, "SKILL.md");
|
|
92
|
-
if (deps.fs.existsSync(skillMd)) {
|
|
93
|
-
return nested;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return dir;
|
|
97
|
-
}
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// Single-source skill loading
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
function loadSkillsFromSourceDir(deps, dir, source, limits) {
|
|
102
|
-
const rootDir = deps.path.resolve(dir);
|
|
103
|
-
let rootRealPath;
|
|
104
|
-
try {
|
|
105
|
-
rootRealPath = deps.fs.realpathSync(rootDir);
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
return [];
|
|
109
|
-
}
|
|
110
|
-
const baseDir = resolveNestedSkillsRoot(deps, rootDir, limits.maxCandidatesPerRoot);
|
|
111
|
-
const baseDirRealPath = resolveContainedPath(deps, rootRealPath, baseDir);
|
|
112
|
-
if (!baseDirRealPath)
|
|
113
|
-
return [];
|
|
114
|
-
// Case 1: root itself is a single skill
|
|
115
|
-
const rootSkillMd = deps.path.join(baseDir, "SKILL.md");
|
|
116
|
-
if (deps.fs.existsSync(rootSkillMd)) {
|
|
117
|
-
if (!resolveContainedPath(deps, baseDirRealPath, rootSkillMd))
|
|
118
|
-
return [];
|
|
119
|
-
try {
|
|
120
|
-
const size = deps.fs.statSync(rootSkillMd).size;
|
|
121
|
-
if (size > limits.maxSkillFileBytes)
|
|
122
|
-
return [];
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
return [];
|
|
126
|
-
}
|
|
127
|
-
return [buildWorkspaceSkill(deps, rootSkillMd, baseDir, source)].filter((s) => s !== null);
|
|
128
|
-
}
|
|
129
|
-
// Case 2: enumerate child directories for `<child>/SKILL.md`
|
|
130
|
-
const childDirs = listChildDirectories(deps, baseDir);
|
|
131
|
-
const maxCandidates = Math.max(0, limits.maxSkillsLoadedPerSource);
|
|
132
|
-
const limitedChildren = childDirs.slice().sort().slice(0, maxCandidates);
|
|
133
|
-
const skills = [];
|
|
134
|
-
for (const name of limitedChildren) {
|
|
135
|
-
const skillDir = deps.path.join(baseDir, name);
|
|
136
|
-
if (!resolveContainedPath(deps, baseDirRealPath, skillDir))
|
|
137
|
-
continue;
|
|
138
|
-
const skillMd = deps.path.join(skillDir, "SKILL.md");
|
|
139
|
-
if (!deps.fs.existsSync(skillMd))
|
|
140
|
-
continue;
|
|
141
|
-
if (!resolveContainedPath(deps, baseDirRealPath, skillMd))
|
|
142
|
-
continue;
|
|
143
|
-
try {
|
|
144
|
-
const size = deps.fs.statSync(skillMd).size;
|
|
145
|
-
if (size > limits.maxSkillFileBytes)
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
const skill = buildWorkspaceSkill(deps, skillMd, skillDir, source);
|
|
152
|
-
if (skill)
|
|
153
|
-
skills.push(skill);
|
|
154
|
-
if (skills.length >= limits.maxSkillsLoadedPerSource)
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
return skills;
|
|
158
|
-
}
|
|
159
|
-
function buildWorkspaceSkill(deps, filePath, baseDir, source) {
|
|
160
|
-
try {
|
|
161
|
-
const content = deps.fs.readFileSync(filePath, "utf-8");
|
|
162
|
-
const fm = parseFrontmatter(content);
|
|
163
|
-
const name = fm["name"] ?? deps.path.resolve(baseDir).split(deps.path.sep).pop() ?? "unknown";
|
|
164
|
-
return {
|
|
165
|
-
name,
|
|
166
|
-
description: fm["description"],
|
|
167
|
-
source,
|
|
168
|
-
filePath: deps.path.resolve(filePath),
|
|
169
|
-
baseDir: deps.path.resolve(baseDir),
|
|
170
|
-
primaryEnv: fm["primaryEnv"] ?? fm["primary-env"],
|
|
171
|
-
requiredEnv: fm["required-env"]?.split(",").map((s) => s.trim()).filter(Boolean),
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
// Public API
|
|
180
|
-
// ---------------------------------------------------------------------------
|
|
181
|
-
/**
|
|
182
|
-
* Discover all skills from configured search paths, applying precedence merge
|
|
183
|
-
* and size/count limits. Returns fully parsed `SkillEntry` objects.
|
|
184
|
-
*/
|
|
185
|
-
export function loadSkillEntries(deps) {
|
|
186
|
-
const limits = resolveLimits(deps.limits);
|
|
187
|
-
const { searchPaths } = deps;
|
|
188
|
-
const sources = [];
|
|
189
|
-
// Order: extra < bundled < managed < personalAgents < projectAgents < workspace
|
|
190
|
-
if (searchPaths.extra) {
|
|
191
|
-
for (const dir of searchPaths.extra) {
|
|
192
|
-
sources.push({ dir, source: "extra" });
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
if (searchPaths.bundled) {
|
|
196
|
-
sources.push({ dir: searchPaths.bundled, source: "bundled" });
|
|
197
|
-
}
|
|
198
|
-
if (searchPaths.managed) {
|
|
199
|
-
sources.push({ dir: searchPaths.managed, source: "managed" });
|
|
200
|
-
}
|
|
201
|
-
if (searchPaths.personalAgents) {
|
|
202
|
-
sources.push({ dir: searchPaths.personalAgents, source: "agents-personal" });
|
|
203
|
-
}
|
|
204
|
-
if (searchPaths.projectAgents) {
|
|
205
|
-
sources.push({ dir: searchPaths.projectAgents, source: "agents-project" });
|
|
206
|
-
}
|
|
207
|
-
if (searchPaths.workspace) {
|
|
208
|
-
sources.push({ dir: searchPaths.workspace, source: "workspace" });
|
|
209
|
-
}
|
|
210
|
-
// Merge with later sources overriding earlier ones (by name)
|
|
211
|
-
const merged = new Map();
|
|
212
|
-
for (const src of sources) {
|
|
213
|
-
const skills = loadSkillsFromSourceDir(deps, src.dir, src.source, limits);
|
|
214
|
-
for (const skill of skills) {
|
|
215
|
-
merged.set(skill.name, skill);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// Build entries with parsed metadata
|
|
219
|
-
const entries = [];
|
|
220
|
-
for (const skill of merged.values()) {
|
|
221
|
-
let frontmatter = parseFrontmatter("");
|
|
222
|
-
try {
|
|
223
|
-
const raw = deps.fs.readFileSync(skill.filePath, "utf-8");
|
|
224
|
-
frontmatter = parseFrontmatter(raw);
|
|
225
|
-
}
|
|
226
|
-
catch {
|
|
227
|
-
// ignore malformed
|
|
228
|
-
}
|
|
229
|
-
entries.push({
|
|
230
|
-
skill,
|
|
231
|
-
frontmatter,
|
|
232
|
-
metadata: resolveSkillMetadata(frontmatter),
|
|
233
|
-
invocation: resolveSkillInvocationPolicy(frontmatter),
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
return entries;
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Filter skill entries by a name-based filter list.
|
|
240
|
-
* If filter is undefined or empty, returns all entries.
|
|
241
|
-
*/
|
|
242
|
-
export function filterSkillEntries(entries, skillFilter) {
|
|
243
|
-
if (!skillFilter || skillFilter.length === 0)
|
|
244
|
-
return entries;
|
|
245
|
-
const normalized = new Set(skillFilter.map((s) => s.trim().toLowerCase()).filter(Boolean));
|
|
246
|
-
if (normalized.size === 0)
|
|
247
|
-
return [];
|
|
248
|
-
return entries.filter((e) => normalized.has(e.skill.name.toLowerCase()));
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Build a system-prompt-embeddable skill snapshot.
|
|
252
|
-
* Applies count and character limits from the provided config.
|
|
253
|
-
*/
|
|
254
|
-
export function buildSkillSnapshot(entries, limits, skillFilter) {
|
|
255
|
-
const resolved = resolveLimits(limits);
|
|
256
|
-
const filtered = filterSkillEntries(entries, skillFilter);
|
|
257
|
-
// Truncate by count first
|
|
258
|
-
let promptSkills = filtered.slice(0, Math.max(0, resolved.maxSkillsInPrompt));
|
|
259
|
-
// Format function for prompt
|
|
260
|
-
const formatForPrompt = (skills) => {
|
|
261
|
-
if (skills.length === 0)
|
|
262
|
-
return "";
|
|
263
|
-
const lines = [];
|
|
264
|
-
for (const entry of skills) {
|
|
265
|
-
const s = entry.skill;
|
|
266
|
-
const meta = entry.metadata;
|
|
267
|
-
const desc = s.description ? ` — ${s.description}` : "";
|
|
268
|
-
const env = s.primaryEnv ? ` [${s.primaryEnv}]` : "";
|
|
269
|
-
const emoji = meta?.emoji ? `${meta.emoji} ` : "";
|
|
270
|
-
lines.push(`- ${emoji}**${s.name}**${env}${desc}`);
|
|
271
|
-
// Compact path with ~ prefix
|
|
272
|
-
lines.push(` File: ${s.filePath}`);
|
|
273
|
-
}
|
|
274
|
-
return lines.join("\n");
|
|
275
|
-
};
|
|
276
|
-
// Truncate by character budget via binary search
|
|
277
|
-
let prompt = formatForPrompt(promptSkills);
|
|
278
|
-
if (prompt.length > resolved.maxSkillsPromptChars) {
|
|
279
|
-
let lo = 0;
|
|
280
|
-
let hi = promptSkills.length;
|
|
281
|
-
while (lo < hi) {
|
|
282
|
-
const mid = Math.ceil((lo + hi) / 2);
|
|
283
|
-
const candidate = formatForPrompt(promptSkills.slice(0, mid));
|
|
284
|
-
if (candidate.length <= resolved.maxSkillsPromptChars) {
|
|
285
|
-
lo = mid;
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
hi = mid - 1;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
promptSkills = promptSkills.slice(0, lo);
|
|
292
|
-
prompt = formatForPrompt(promptSkills);
|
|
293
|
-
}
|
|
294
|
-
return {
|
|
295
|
-
prompt,
|
|
296
|
-
skills: promptSkills.map((e) => ({
|
|
297
|
-
name: e.skill.name,
|
|
298
|
-
primaryEnv: e.skill.primaryEnv,
|
|
299
|
-
requiredEnv: e.skill.requiredEnv,
|
|
300
|
-
})),
|
|
301
|
-
skillFilter,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Skill source abstraction — defines where skills come from and how they
|
|
3
|
-
// are fetched/installed. Consumers implement specific source adapters.
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
/**
|
|
6
|
-
* Orchestrate the full install pipeline:
|
|
7
|
-
* Fetch → Quarantine → Scan → Validate → Move → Lock
|
|
8
|
-
*
|
|
9
|
-
* This function is pure orchestration — actual I/O is delegated to deps.
|
|
10
|
-
*/
|
|
11
|
-
export async function installSkill(params) {
|
|
12
|
-
const { source, fetchDeps, storageDeps, scanSkillDir, quarantineDir } = params;
|
|
13
|
-
// Step 1: Fetch to quarantine
|
|
14
|
-
let fetchResult;
|
|
15
|
-
try {
|
|
16
|
-
fetchResult = await fetchDeps.fetch(source, quarantineDir);
|
|
17
|
-
}
|
|
18
|
-
catch (err) {
|
|
19
|
-
return {
|
|
20
|
-
success: false,
|
|
21
|
-
name: source.identifier,
|
|
22
|
-
verdict: "reject",
|
|
23
|
-
error: `Fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
const skillName = fetchResult.name ?? source.identifier.split("/").pop() ?? "unknown";
|
|
27
|
-
// Step 2: Security scan
|
|
28
|
-
let scanSummary;
|
|
29
|
-
try {
|
|
30
|
-
scanSummary = await scanSkillDir(fetchResult.quarantineDir);
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
return {
|
|
34
|
-
success: false,
|
|
35
|
-
name: skillName,
|
|
36
|
-
verdict: "reject",
|
|
37
|
-
error: `Scan failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
// Step 3: Verdict
|
|
41
|
-
let verdict;
|
|
42
|
-
if (scanSummary.critical > 0) {
|
|
43
|
-
verdict = source.trust === "builtin" ? "quarantine" : "reject";
|
|
44
|
-
}
|
|
45
|
-
else if (scanSummary.warn > 0) {
|
|
46
|
-
verdict = "quarantine";
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
verdict = "pass";
|
|
50
|
-
}
|
|
51
|
-
if (verdict === "reject") {
|
|
52
|
-
return {
|
|
53
|
-
success: false,
|
|
54
|
-
name: skillName,
|
|
55
|
-
verdict,
|
|
56
|
-
scanSummary,
|
|
57
|
-
error: `Security scan rejected: ${scanSummary.critical} critical finding(s)`,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
// Step 4: Move to install location
|
|
61
|
-
let installDir;
|
|
62
|
-
try {
|
|
63
|
-
installDir = await storageDeps.moveToInstallDir(fetchResult.quarantineDir, skillName);
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
return {
|
|
67
|
-
success: false,
|
|
68
|
-
name: skillName,
|
|
69
|
-
verdict,
|
|
70
|
-
scanSummary,
|
|
71
|
-
error: `Install move failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
// Step 5: Update lockfile
|
|
75
|
-
try {
|
|
76
|
-
const lockfile = await storageDeps.readLockfile();
|
|
77
|
-
lockfile.entries[skillName] = {
|
|
78
|
-
name: skillName,
|
|
79
|
-
source,
|
|
80
|
-
installedAt: new Date().toISOString(),
|
|
81
|
-
scanVerdict: verdict,
|
|
82
|
-
scanSummary,
|
|
83
|
-
};
|
|
84
|
-
await storageDeps.writeLockfile(lockfile);
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// Non-fatal: skill is installed but lock may be stale
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
success: true,
|
|
91
|
-
name: skillName,
|
|
92
|
-
installDir,
|
|
93
|
-
verdict,
|
|
94
|
-
scanSummary,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Uninstall a skill: remove from disk + update lockfile.
|
|
99
|
-
*/
|
|
100
|
-
export async function uninstallSkill(params) {
|
|
101
|
-
const { skillName, storageDeps } = params;
|
|
102
|
-
try {
|
|
103
|
-
await storageDeps.removeSkill(skillName);
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
return {
|
|
107
|
-
success: false,
|
|
108
|
-
error: `Remove failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
const lockfile = await storageDeps.readLockfile();
|
|
113
|
-
delete lockfile.entries[skillName];
|
|
114
|
-
await storageDeps.writeLockfile(lockfile);
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// Non-fatal
|
|
118
|
-
}
|
|
119
|
-
return { success: true };
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Create an empty lockfile.
|
|
123
|
-
*/
|
|
124
|
-
export function createEmptyLockfile() {
|
|
125
|
-
return { version: 1, entries: {} };
|
|
126
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Skill system type definitions — portable, runtime-agnostic.
|
|
3
|
-
// Mirrors openclaw/src/agents/skills/types.ts + security/skill-scanner.ts
|
|
4
|
-
// but decoupled from framework internals.
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
export {};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Think Tool — explicit reasoning before complex decisions.
|
|
3
|
-
//
|
|
4
|
-
// The Agent calls `think` to reason step-by-step before deciding
|
|
5
|
-
// which tools to use. The thought is never shown to the user.
|
|
6
|
-
// Pure no-op: zero side effects, zero dependencies.
|
|
7
|
-
// ============================================================
|
|
8
|
-
export const THINK_TOOL_NAME = "think";
|
|
9
|
-
export const THINK_TOOL_SCHEMA = {
|
|
10
|
-
type: "object",
|
|
11
|
-
properties: {
|
|
12
|
-
thought: {
|
|
13
|
-
type: "string",
|
|
14
|
-
description: [
|
|
15
|
-
"Your internal reasoning about the current situation.",
|
|
16
|
-
"Use this to:",
|
|
17
|
-
"• Analyze what the user really wants (disambiguate vague requests)",
|
|
18
|
-
"• Plan multi-step approaches before executing",
|
|
19
|
-
"• Evaluate which tool(s) to use and why",
|
|
20
|
-
"• Consider edge cases or potential issues",
|
|
21
|
-
"• Reflect on conversation context and user preferences",
|
|
22
|
-
"This content is never shown to the user.",
|
|
23
|
-
].join("\n"),
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
required: ["thought"],
|
|
27
|
-
};
|
|
28
|
-
export function createThinkTool() {
|
|
29
|
-
return {
|
|
30
|
-
name: THINK_TOOL_NAME,
|
|
31
|
-
label: "Think",
|
|
32
|
-
shouldDefer: true,
|
|
33
|
-
description: [
|
|
34
|
-
"Use this tool to think and reason about the current situation BEFORE taking action.",
|
|
35
|
-
"Call this tool when you need to:",
|
|
36
|
-
"- Analyze an ambiguous or complex user request before deciding what to do",
|
|
37
|
-
"- Plan a multi-step approach (what tools to call and in what order)",
|
|
38
|
-
"- Identify which steps can run in parallel vs. which must be sequential",
|
|
39
|
-
"- Evaluate tradeoffs between different approaches",
|
|
40
|
-
"- Reflect on user preferences from conversation history",
|
|
41
|
-
"- Process new information that changes your understanding",
|
|
42
|
-
"",
|
|
43
|
-
"This tool has NO side effects — it simply records your reasoning process.",
|
|
44
|
-
"Your thought is NOT shown to the user; it only improves YOUR decision quality.",
|
|
45
|
-
"After thinking, proceed to take the appropriate action.",
|
|
46
|
-
"",
|
|
47
|
-
"When planning multi-step work, be explicit about parallelism:",
|
|
48
|
-
'- Say "这些可以并行生成" or "these can run in parallel" for independent tasks.',
|
|
49
|
-
'- Say "先…然后…" or "step 1 first, then step 2" for sequential dependencies.',
|
|
50
|
-
].join("\n"),
|
|
51
|
-
parameters: THINK_TOOL_SCHEMA,
|
|
52
|
-
execute: async (_toolCallId, _params) => {
|
|
53
|
-
return {
|
|
54
|
-
content: [{ type: "text", text: "Thought recorded. Now proceed with the best action based on your reasoning." }],
|
|
55
|
-
details: { type: "think" },
|
|
56
|
-
};
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
}
|
package/dist/skills/todo-tool.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Todo Tool — session-level structured task management.
|
|
3
|
-
//
|
|
4
|
-
// Allows the agent to create, update, and track multi-step task
|
|
5
|
-
// lists during complex conversations. Each tool invocation
|
|
6
|
-
// replaces the full todo list (same pattern as Copilot's
|
|
7
|
-
// manage_todo_list).
|
|
8
|
-
//
|
|
9
|
-
// Aligned with Claude Code TodoWriteTool:
|
|
10
|
-
// - activeForm field (present-tense text for UI spinners)
|
|
11
|
-
// - oldTodos snapshot returned for diffing
|
|
12
|
-
// - Auto-clear when all items completed
|
|
13
|
-
// - verificationNudgeNeeded signal
|
|
14
|
-
// - Per-agent isolation via agentId option
|
|
15
|
-
//
|
|
16
|
-
// Reference: claude-code-haha/src/tools/TodoWriteTool/TodoWriteTool.ts
|
|
17
|
-
// ============================================================
|
|
18
|
-
import { summarizeTodoList } from "../contracts/todo.js";
|
|
19
|
-
export const TODO_TOOL_NAME = "todo";
|
|
20
|
-
export const TODO_TOOL_SCHEMA = {
|
|
21
|
-
type: "object",
|
|
22
|
-
properties: {
|
|
23
|
-
todoList: {
|
|
24
|
-
type: "array",
|
|
25
|
-
description: "Complete array of ALL todo items. Must include both existing and new items. " +
|
|
26
|
-
"Only one item may be in-progress at a time. Mark todos completed " +
|
|
27
|
-
"IMMEDIATELY after finishing — do not batch completions.",
|
|
28
|
-
items: {
|
|
29
|
-
type: "object",
|
|
30
|
-
properties: {
|
|
31
|
-
id: { type: "number", description: "Unique numeric id (sequential from 1)." },
|
|
32
|
-
title: { type: "string", description: "Concise action-oriented label (3-7 words). Displayed in UI." },
|
|
33
|
-
status: {
|
|
34
|
-
type: "string",
|
|
35
|
-
enum: ["not-started", "in-progress", "completed"],
|
|
36
|
-
description: "not-started | in-progress (max 1) | completed",
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
required: ["id", "title", "status"],
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
required: ["todoList"],
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* Create a stateful todo tool instance.
|
|
47
|
-
*
|
|
48
|
-
* Each call returns a tool with its own session-local list.
|
|
49
|
-
* Multiple instances can exist for different sessions/agents.
|
|
50
|
-
*/
|
|
51
|
-
export function createTodoTool(options) {
|
|
52
|
-
let currentList = [];
|
|
53
|
-
const opts = options ?? {};
|
|
54
|
-
return {
|
|
55
|
-
name: TODO_TOOL_NAME,
|
|
56
|
-
label: "Todo",
|
|
57
|
-
description: "Manage a structured todo list to track progress on multi-step tasks. " +
|
|
58
|
-
"Each call replaces the full list. Use frequently during complex work " +
|
|
59
|
-
"to plan steps and show progress. Mark items in-progress before starting, " +
|
|
60
|
-
"completed immediately after finishing.",
|
|
61
|
-
parameters: TODO_TOOL_SCHEMA,
|
|
62
|
-
searchHint: "manage session task checklist progress tracking",
|
|
63
|
-
maxResultSizeChars: 100_000,
|
|
64
|
-
execute: async (_toolCallId, params) => {
|
|
65
|
-
const items = params.todoList;
|
|
66
|
-
// Validate: at most 1 in-progress
|
|
67
|
-
const inProgress = items.filter((t) => t.status === "in-progress");
|
|
68
|
-
if (inProgress.length > 1) {
|
|
69
|
-
return {
|
|
70
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
71
|
-
warning: `Only 1 item may be in-progress at a time (found ${inProgress.length}).`,
|
|
72
|
-
todoList: currentList,
|
|
73
|
-
}) }],
|
|
74
|
-
details: { type: "todo", warning: "multiple_in_progress" },
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
// Capture old state for diff (CC-aligned: returns oldTodos)
|
|
78
|
-
const oldTodos = [...currentList];
|
|
79
|
-
// Check if all items are completed
|
|
80
|
-
const allDone = items.length > 0 && items.every((t) => t.status === "completed");
|
|
81
|
-
// Auto-clear when all completed (CC behavior)
|
|
82
|
-
if (allDone && opts.autoClearOnComplete) {
|
|
83
|
-
currentList = [];
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
currentList = items;
|
|
87
|
-
}
|
|
88
|
-
const summary = summarizeTodoList(items);
|
|
89
|
-
// Verification nudge: ≥3 items, all done, none matching /verif/i
|
|
90
|
-
let verificationNudgeNeeded = false;
|
|
91
|
-
if (opts.enableVerificationNudge &&
|
|
92
|
-
allDone &&
|
|
93
|
-
items.length >= 3 &&
|
|
94
|
-
!items.some((t) => /verif/i.test(t.title))) {
|
|
95
|
-
verificationNudgeNeeded = true;
|
|
96
|
-
}
|
|
97
|
-
const result = {
|
|
98
|
-
total: summary.total,
|
|
99
|
-
completed: summary.completed,
|
|
100
|
-
inProgress: summary.inProgress,
|
|
101
|
-
notStarted: summary.notStarted,
|
|
102
|
-
todoList: currentList,
|
|
103
|
-
oldTodos,
|
|
104
|
-
};
|
|
105
|
-
if (verificationNudgeNeeded) {
|
|
106
|
-
result.verificationNudgeNeeded = true;
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
110
|
-
details: { type: "todo", ...summary, verificationNudgeNeeded, agentId: opts.agentId },
|
|
111
|
-
};
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
}
|