@renxqoo/renx-code 0.0.4 → 0.0.6
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/README.md +82 -51
- package/bin/renx.cjs +16 -0
- package/package.json +2 -45
- package/src/agent/runtime/runtime.context-usage.test.ts +4 -5
- package/src/agent/runtime/runtime.error-handling.test.ts +4 -5
- package/src/agent/runtime/runtime.test.ts +7 -4
- package/src/agent/runtime/runtime.ts +3 -9
- package/src/agent/runtime/runtime.usage-forwarding.test.ts +4 -5
- package/src/agent/runtime/source-modules.test.ts +16 -35
- package/src/agent/runtime/source-modules.ts +17 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_ACCEPTANCE_CHECKLIST.md +95 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.html +1345 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.md +1353 -0
- package/vendor/agent-root/src/agent/ERROR_CONTRACT.md +60 -0
- package/vendor/agent-root/src/agent/TEST_COVERAGE_ANALYSIS.md +278 -0
- package/vendor/agent-root/src/agent/__test__/error-contract.test.ts +72 -0
- package/vendor/agent-root/src/agent/__test__/types.test.ts +137 -0
- package/vendor/agent-root/src/agent/agent/__test__/abort-runtime.test.ts +83 -0
- package/vendor/agent-root/src/agent/agent/__test__/callback-safety.test.ts +34 -0
- package/vendor/agent-root/src/agent/agent/__test__/compaction.test.ts +323 -0
- package/vendor/agent-root/src/agent/agent/__test__/concurrency.test.ts +290 -0
- package/vendor/agent-root/src/agent/agent/__test__/error-normalizer.test.ts +377 -0
- package/vendor/agent-root/src/agent/agent/__test__/error.test.ts +212 -0
- package/vendor/agent-root/src/agent/agent/__test__/fault-injection.test.ts +295 -0
- package/vendor/agent-root/src/agent/agent/__test__/index.test.ts +3607 -0
- package/vendor/agent-root/src/agent/agent/__test__/logger.test.ts +35 -0
- package/vendor/agent-root/src/agent/agent/__test__/message-utils.test.ts +517 -0
- package/vendor/agent-root/src/agent/agent/__test__/telemetry.test.ts +97 -0
- package/vendor/agent-root/src/agent/agent/__test__/timeout-budget.test.ts +479 -0
- package/vendor/agent-root/src/agent/agent/__test__/tool-call-merge.test.ts +80 -0
- package/vendor/agent-root/src/agent/agent/__test__/tool-execution-ledger.test.ts +76 -0
- package/vendor/agent-root/src/agent/agent/__test__/write-buffer.test.ts +173 -0
- package/vendor/agent-root/src/agent/agent/__test__/write-file-session.test.ts +109 -0
- package/vendor/agent-root/src/agent/agent/abort-runtime.ts +71 -0
- package/vendor/agent-root/src/agent/agent/callback-safety.ts +33 -0
- package/vendor/agent-root/src/agent/agent/compaction.ts +291 -0
- package/vendor/agent-root/src/agent/agent/concurrency.ts +103 -0
- package/vendor/agent-root/src/agent/agent/error-normalizer.ts +190 -0
- package/vendor/agent-root/src/agent/agent/error.ts +198 -0
- package/vendor/agent-root/src/agent/agent/index.ts +1772 -0
- package/vendor/agent-root/src/agent/agent/logger.ts +65 -0
- package/vendor/agent-root/src/agent/agent/message-utils.ts +101 -0
- package/vendor/agent-root/src/agent/agent/stream-events.ts +61 -0
- package/vendor/agent-root/src/agent/agent/telemetry.ts +123 -0
- package/vendor/agent-root/src/agent/agent/timeout-budget.ts +227 -0
- package/vendor/agent-root/src/agent/agent/tool-call-merge.ts +111 -0
- package/vendor/agent-root/src/agent/agent/tool-execution-ledger.ts +164 -0
- package/vendor/agent-root/src/agent/agent/write-buffer.ts +188 -0
- package/vendor/agent-root/src/agent/agent/write-file-session.ts +238 -0
- package/vendor/agent-root/src/agent/app/__test__/agent-app-service.test.ts +1053 -0
- package/vendor/agent-root/src/agent/app/__test__/minimal-agent-application.test.ts +158 -0
- package/vendor/agent-root/src/agent/app/__test__/sqlite-agent-app-store.test.ts +437 -0
- package/vendor/agent-root/src/agent/app/agent-app-service.ts +748 -0
- package/vendor/agent-root/src/agent/app/contracts.ts +109 -0
- package/vendor/agent-root/src/agent/app/index.ts +5 -0
- package/vendor/agent-root/src/agent/app/minimal-agent-application.ts +151 -0
- package/vendor/agent-root/src/agent/app/ports.ts +72 -0
- package/vendor/agent-root/src/agent/app/sqlite-agent-app-store.ts +1182 -0
- package/vendor/agent-root/src/agent/app/sqlite-client.ts +177 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/00-README.md +36 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/01-scope-and-goals.md +33 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/02-architecture-overview.md +40 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/03-domain-model-and-contracts.md +91 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/04-ports-and-interfaces.md +116 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/05-run-orchestration-and-state-machine.md +52 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/06-cli-commands-and-ux.md +53 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/07-storage-design-local.md +52 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/08-error-and-observability.md +40 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/09-security-and-policy-boundary.md +19 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/10-test-plan-and-acceptance.md +28 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/11-implementation-phases.md +26 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/12-open-questions-and-risks.md +30 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/13-sqlite-schema-fields-and-rationale.md +567 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/14-project-flow-mermaid.md +583 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/15-openclaw-style-project-blueprint.md +972 -0
- package/vendor/agent-root/src/agent/error-contract.ts +154 -0
- package/vendor/agent-root/src/agent/prompts/system.ts +246 -0
- package/vendor/agent-root/src/agent/prompts/system1.ts +208 -0
- package/vendor/agent-root/src/agent/storage/__test__/file-history-store.test.ts +98 -0
- package/vendor/agent-root/src/agent/storage/file-history-store.ts +313 -0
- package/vendor/agent-root/src/agent/storage/file-storage-config.ts +94 -0
- package/vendor/agent-root/src/agent/storage/file-system.ts +31 -0
- package/vendor/agent-root/src/agent/storage/file-write-service.ts +21 -0
- package/vendor/agent-root/src/agent/tool/__test__/base-tool.test.ts +413 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash-policy.test.ts +356 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash.mocked-coverage.test.ts +375 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash.test.ts +372 -0
- package/vendor/agent-root/src/agent/tool/__test__/error.test.ts +108 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-edit-tool.test.ts +258 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-history-tools.test.ts +121 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-read-tool.test.ts +210 -0
- package/vendor/agent-root/src/agent/tool/__test__/glob.test.ts +139 -0
- package/vendor/agent-root/src/agent/tool/__test__/grep.mocked-coverage.test.ts +456 -0
- package/vendor/agent-root/src/agent/tool/__test__/grep.test.ts +192 -0
- package/vendor/agent-root/src/agent/tool/__test__/lsp.test.ts +300 -0
- package/vendor/agent-root/src/agent/tool/__test__/outside-workspace-confirmation.test.ts +214 -0
- package/vendor/agent-root/src/agent/tool/__test__/path-security.test.ts +336 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-loader.test.ts +494 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-parser.test.ts +543 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-tool.test.ts +172 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-concurrency-and-version.test.ts +116 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-create-get-list-update.test.ts +267 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-create.test.ts +519 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-errors.test.ts +225 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-output-blocking.test.ts +223 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-output.test.ts +184 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-parent-abort.test.ts +287 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-real-runner-adapter.test.ts +190 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-run-lifecycle.test.ts +352 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-store-runner-branches.test.ts +395 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-store.test.ts +391 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config-integration.test.ts +176 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config.test.ts +68 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-tools-core-edges.test.ts +630 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-tools-runtime-edges.test.ts +732 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-types.test.ts +494 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-utils-branches.test.ts +175 -0
- package/vendor/agent-root/src/agent/tool/__test__/tool-manager.test.ts +505 -0
- package/vendor/agent-root/src/agent/tool/__test__/types.test.ts +55 -0
- package/vendor/agent-root/src/agent/tool/__test__/web-fetch.test.ts +244 -0
- package/vendor/agent-root/src/agent/tool/__test__/web-search.test.ts +290 -0
- package/vendor/agent-root/src/agent/tool/__test__/write-file.test.ts +368 -0
- package/vendor/agent-root/src/agent/tool/base-tool.ts +345 -0
- package/vendor/agent-root/src/agent/tool/bash-policy.ts +636 -0
- package/vendor/agent-root/src/agent/tool/bash.ts +688 -0
- package/vendor/agent-root/src/agent/tool/error.ts +131 -0
- package/vendor/agent-root/src/agent/tool/file-edit-tool.ts +264 -0
- package/vendor/agent-root/src/agent/tool/file-history-list.ts +103 -0
- package/vendor/agent-root/src/agent/tool/file-history-restore.ts +149 -0
- package/vendor/agent-root/src/agent/tool/file-read-tool.ts +211 -0
- package/vendor/agent-root/src/agent/tool/glob.ts +171 -0
- package/vendor/agent-root/src/agent/tool/grep.ts +496 -0
- package/vendor/agent-root/src/agent/tool/lsp.ts +481 -0
- package/vendor/agent-root/src/agent/tool/path-security.ts +117 -0
- package/vendor/agent-root/src/agent/tool/search/common.ts +153 -0
- package/vendor/agent-root/src/agent/tool/skill/index.ts +13 -0
- package/vendor/agent-root/src/agent/tool/skill/loader.ts +229 -0
- package/vendor/agent-root/src/agent/tool/skill/parser.ts +124 -0
- package/vendor/agent-root/src/agent/tool/skill/types.ts +27 -0
- package/vendor/agent-root/src/agent/tool/skill-tool.ts +143 -0
- package/vendor/agent-root/src/agent/tool/task-create.ts +186 -0
- package/vendor/agent-root/src/agent/tool/task-errors.ts +42 -0
- package/vendor/agent-root/src/agent/tool/task-get.ts +116 -0
- package/vendor/agent-root/src/agent/tool/task-graph.ts +78 -0
- package/vendor/agent-root/src/agent/tool/task-list.ts +141 -0
- package/vendor/agent-root/src/agent/tool/task-mock-runner-adapter.ts +232 -0
- package/vendor/agent-root/src/agent/tool/task-output.ts +223 -0
- package/vendor/agent-root/src/agent/tool/task-parent-abort.ts +115 -0
- package/vendor/agent-root/src/agent/tool/task-real-runner-adapter.ts +336 -0
- package/vendor/agent-root/src/agent/tool/task-runner-adapter.ts +55 -0
- package/vendor/agent-root/src/agent/tool/task-stop.ts +187 -0
- package/vendor/agent-root/src/agent/tool/task-store.ts +217 -0
- package/vendor/agent-root/src/agent/tool/task-subagent-config.ts +149 -0
- package/vendor/agent-root/src/agent/tool/task-types.ts +264 -0
- package/vendor/agent-root/src/agent/tool/task-update.ts +315 -0
- package/vendor/agent-root/src/agent/tool/task.ts +209 -0
- package/vendor/agent-root/src/agent/tool/tool-manager.ts +362 -0
- package/vendor/agent-root/src/agent/tool/tool-prompts.ts +242 -0
- package/vendor/agent-root/src/agent/tool/types.ts +116 -0
- package/vendor/agent-root/src/agent/tool/web-fetch.ts +227 -0
- package/vendor/agent-root/src/agent/tool/web-search.ts +208 -0
- package/vendor/agent-root/src/agent/tool/write-file.ts +497 -0
- package/vendor/agent-root/src/agent/types.ts +232 -0
- package/vendor/agent-root/src/agent/utils/__tests__/index.test.ts +18 -0
- package/vendor/agent-root/src/agent/utils/__tests__/message-utils.test.ts +610 -0
- package/vendor/agent-root/src/agent/utils/__tests__/message.test.ts +223 -0
- package/vendor/agent-root/src/agent/utils/__tests__/token.test.ts +42 -0
- package/vendor/agent-root/src/agent/utils/index.ts +16 -0
- package/vendor/agent-root/src/agent/utils/message.ts +171 -0
- package/vendor/agent-root/src/agent/utils/token.ts +28 -0
- package/vendor/agent-root/src/config/__tests__/load-config-to-env.test.ts +129 -0
- package/vendor/agent-root/src/config/__tests__/loader.test.ts +247 -0
- package/vendor/agent-root/src/config/__tests__/runtime.test.ts +88 -0
- package/vendor/agent-root/src/config/index.ts +54 -0
- package/vendor/agent-root/src/config/loader.ts +431 -0
- package/vendor/agent-root/src/config/paths.ts +30 -0
- package/vendor/agent-root/src/config/runtime.ts +163 -0
- package/vendor/agent-root/src/config/types.ts +70 -0
- package/vendor/agent-root/src/logger/index.ts +57 -0
- package/vendor/agent-root/src/logger/logger.ts +819 -0
- package/vendor/agent-root/src/logger/types.ts +150 -0
- package/vendor/agent-root/src/providers/__tests__/errors.test.ts +441 -0
- package/vendor/agent-root/src/providers/__tests__/index.test.ts +16 -0
- package/vendor/agent-root/src/providers/__tests__/openai-compatible.options.test.ts +318 -0
- package/vendor/agent-root/src/providers/__tests__/openai-compatible.test.ts +600 -0
- package/vendor/agent-root/src/providers/__tests__/registry.test.ts +449 -0
- package/vendor/agent-root/src/providers/__tests__/responses-adapter.test.ts +298 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/anthropic.test.ts +354 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/kimi.test.ts +58 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/standard.test.ts +261 -0
- package/vendor/agent-root/src/providers/adapters/anthropic.ts +572 -0
- package/vendor/agent-root/src/providers/adapters/base.ts +131 -0
- package/vendor/agent-root/src/providers/adapters/kimi.ts +48 -0
- package/vendor/agent-root/src/providers/adapters/responses.ts +732 -0
- package/vendor/agent-root/src/providers/adapters/standard.ts +120 -0
- package/vendor/agent-root/src/providers/http/__tests__/client.timeout.test.ts +313 -0
- package/vendor/agent-root/src/providers/http/client.ts +289 -0
- package/vendor/agent-root/src/providers/http/stream-parser.ts +109 -0
- package/vendor/agent-root/src/providers/index.ts +76 -0
- package/vendor/agent-root/src/providers/kimi-headers.ts +177 -0
- package/vendor/agent-root/src/providers/openai-compatible.ts +387 -0
- package/vendor/agent-root/src/providers/registry/model-config.ts +230 -0
- package/vendor/agent-root/src/providers/registry/provider-factory.ts +123 -0
- package/vendor/agent-root/src/providers/registry.ts +135 -0
- package/vendor/agent-root/src/providers/types/api.ts +284 -0
- package/vendor/agent-root/src/providers/types/config.ts +58 -0
- package/vendor/agent-root/src/providers/types/errors.ts +323 -0
- package/vendor/agent-root/src/providers/types/index.ts +72 -0
- package/vendor/agent-root/src/providers/types/provider.ts +45 -0
- package/vendor/agent-root/src/providers/types/registry.ts +88 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { SkillTool } from '../skill-tool';
|
|
6
|
+
import { initializeSkillLoader, resetSkillLoader } from '../skill/loader';
|
|
7
|
+
import * as loaderModule from '../skill/loader';
|
|
8
|
+
|
|
9
|
+
describe('SkillTool', () => {
|
|
10
|
+
let rootDir: string;
|
|
11
|
+
let skillsRoot: string;
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
resetSkillLoader();
|
|
15
|
+
rootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'renx-skill-tool-'));
|
|
16
|
+
skillsRoot = path.join(rootDir, 'skills');
|
|
17
|
+
|
|
18
|
+
await fs.mkdir(path.join(skillsRoot, 'test-skill'), { recursive: true });
|
|
19
|
+
await fs.mkdir(path.join(skillsRoot, 'plain-skill'), { recursive: true });
|
|
20
|
+
|
|
21
|
+
await fs.writeFile(
|
|
22
|
+
path.join(skillsRoot, 'test-skill', 'SKILL.md'),
|
|
23
|
+
`---
|
|
24
|
+
name: test-skill
|
|
25
|
+
description: Test workflow skill
|
|
26
|
+
---
|
|
27
|
+
# Test Skill
|
|
28
|
+
|
|
29
|
+
Use @src/app.ts.
|
|
30
|
+
|
|
31
|
+
Run !\`pnpm test\`.`,
|
|
32
|
+
'utf8'
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
await fs.writeFile(
|
|
36
|
+
path.join(skillsRoot, 'plain-skill', 'SKILL.md'),
|
|
37
|
+
`# Plain Skill
|
|
38
|
+
|
|
39
|
+
This is a plain skill without frontmatter.`,
|
|
40
|
+
'utf8'
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(async () => {
|
|
45
|
+
resetSkillLoader();
|
|
46
|
+
await fs.rm(rootDir, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('includes available skills in description after loader initialization', async () => {
|
|
50
|
+
await initializeSkillLoader({ skillRoots: [skillsRoot] });
|
|
51
|
+
const tool = new SkillTool({ loaderOptions: { skillRoots: [skillsRoot] } });
|
|
52
|
+
|
|
53
|
+
expect(tool.description).toContain('Available skills');
|
|
54
|
+
expect(tool.description).toContain('test-skill');
|
|
55
|
+
expect(tool.description).toContain('plain-skill');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('loads skill content by name', async () => {
|
|
59
|
+
const tool = new SkillTool({ loaderOptions: { skillRoots: [skillsRoot] } });
|
|
60
|
+
const result = await tool.execute({ name: 'test-skill' });
|
|
61
|
+
|
|
62
|
+
expect(result.success).toBe(true);
|
|
63
|
+
expect(result.output).toContain('## Skill: test-skill');
|
|
64
|
+
|
|
65
|
+
const metadata = result.metadata as {
|
|
66
|
+
name: string;
|
|
67
|
+
fileRefs: string[];
|
|
68
|
+
shellCommands: string[];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
expect(metadata.name).toBe('test-skill');
|
|
72
|
+
expect(metadata.fileRefs).toContain('src/app.ts');
|
|
73
|
+
expect(metadata.shellCommands).toContain('pnpm test');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('returns SKILL_NOT_FOUND with available skills suggestion', async () => {
|
|
77
|
+
const tool = new SkillTool({ loaderOptions: { skillRoots: [skillsRoot] } });
|
|
78
|
+
const result = await tool.execute({ name: 'missing-skill' });
|
|
79
|
+
|
|
80
|
+
expect(result.success).toBe(false);
|
|
81
|
+
expect(result.output).toContain('SKILL_NOT_FOUND');
|
|
82
|
+
expect((result.metadata as { suggestion: string }).suggestion).toContain('test-skill');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('supports simple mode without skill list in description', () => {
|
|
86
|
+
const tool = new SkillTool({
|
|
87
|
+
includeSkillList: false,
|
|
88
|
+
loaderOptions: { skillRoots: [skillsRoot] },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(tool.description).not.toContain('Available skills');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('exposes parallel-safe concurrency policy and lock key', () => {
|
|
95
|
+
const tool = new SkillTool({ loaderOptions: { skillRoots: [skillsRoot] } });
|
|
96
|
+
|
|
97
|
+
expect(tool.getConcurrencyMode()).toBe('parallel-safe');
|
|
98
|
+
expect(tool.getConcurrencyLockKey({ name: 'test-skill' })).toBe('skill:test-skill');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('refreshDescription invalidates cache', async () => {
|
|
102
|
+
await initializeSkillLoader({ skillRoots: [skillsRoot] });
|
|
103
|
+
const tool = new SkillTool({ loaderOptions: { skillRoots: [skillsRoot] } });
|
|
104
|
+
const before = tool.description;
|
|
105
|
+
tool.refreshDescription();
|
|
106
|
+
const after = tool.description;
|
|
107
|
+
|
|
108
|
+
expect(before).toContain('Available skills');
|
|
109
|
+
expect(after).toContain('Available skills');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('returns SKILL_LOAD_FAILED when metadata exists but file cannot be loaded', async () => {
|
|
113
|
+
await initializeSkillLoader({ skillRoots: [skillsRoot] });
|
|
114
|
+
await fs.rm(path.join(skillsRoot, 'test-skill', 'SKILL.md'));
|
|
115
|
+
|
|
116
|
+
const tool = new SkillTool({ loaderOptions: { skillRoots: [skillsRoot] } });
|
|
117
|
+
const result = await tool.execute({ name: 'test-skill' });
|
|
118
|
+
|
|
119
|
+
expect(result.success).toBe(false);
|
|
120
|
+
expect(result.output).toContain('SKILL_LOAD_FAILED');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('maps unexpected initialize errors to ToolExecutionError', async () => {
|
|
124
|
+
const initSpy = vi
|
|
125
|
+
.spyOn(loaderModule, 'initializeSkillLoader')
|
|
126
|
+
.mockRejectedValueOnce(new Error('loader exploded'));
|
|
127
|
+
|
|
128
|
+
const tool = new SkillTool({ loaderOptions: { skillRoots: [skillsRoot] } });
|
|
129
|
+
const result = await tool.execute({ name: 'test-skill' });
|
|
130
|
+
|
|
131
|
+
expect(result.success).toBe(false);
|
|
132
|
+
expect(result.output).toBe('loader exploded');
|
|
133
|
+
expect(result.error?.name).toBe('ToolExecutionError');
|
|
134
|
+
initSpy.mockRestore();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('shows empty-skill suggestions when no skills are available', async () => {
|
|
138
|
+
const emptyRoot = path.join(rootDir, 'empty-skills');
|
|
139
|
+
await fs.mkdir(emptyRoot, { recursive: true });
|
|
140
|
+
|
|
141
|
+
const tool = new SkillTool({ loaderOptions: { skillRoots: [emptyRoot] } });
|
|
142
|
+
const result = await tool.execute({ name: 'missing-skill' });
|
|
143
|
+
|
|
144
|
+
expect(result.success).toBe(false);
|
|
145
|
+
expect((result.metadata as { suggestion: string }).suggestion).toBe(
|
|
146
|
+
'No skills are currently available.'
|
|
147
|
+
);
|
|
148
|
+
expect(result.output).toContain('No skills are currently available.');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('renders description with no available skills', async () => {
|
|
152
|
+
const emptyRoot = path.join(rootDir, 'description-empty');
|
|
153
|
+
await fs.mkdir(emptyRoot, { recursive: true });
|
|
154
|
+
await initializeSkillLoader({ skillRoots: [emptyRoot] });
|
|
155
|
+
|
|
156
|
+
const tool = new SkillTool({ loaderOptions: { skillRoots: [emptyRoot] } });
|
|
157
|
+
expect(tool.description).toContain('No skills are currently available.');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('maps non-error loader failures using String(error)', async () => {
|
|
161
|
+
const initSpy = vi
|
|
162
|
+
.spyOn(loaderModule, 'initializeSkillLoader')
|
|
163
|
+
.mockRejectedValueOnce('loader string failure');
|
|
164
|
+
|
|
165
|
+
const tool = new SkillTool({ loaderOptions: { skillRoots: [skillsRoot] } });
|
|
166
|
+
const result = await tool.execute({ name: 'test-skill' });
|
|
167
|
+
|
|
168
|
+
expect(result.success).toBe(false);
|
|
169
|
+
expect(result.output).toBe('loader string failure');
|
|
170
|
+
initSpy.mockRestore();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as os from 'node:os';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { TaskStore } from '../task-store';
|
|
6
|
+
import { TaskCreateTool } from '../task-create';
|
|
7
|
+
import { TaskUpdateTool } from '../task-update';
|
|
8
|
+
import { TaskGetTool } from '../task-get';
|
|
9
|
+
|
|
10
|
+
function parseOutput<T>(output: string | undefined): T {
|
|
11
|
+
return JSON.parse(output || '{}') as T;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('task_update versioning and dependency integrity', () => {
|
|
15
|
+
let baseDir: string;
|
|
16
|
+
let store: TaskStore;
|
|
17
|
+
let taskCreate: TaskCreateTool;
|
|
18
|
+
let taskUpdate: TaskUpdateTool;
|
|
19
|
+
let taskGet: TaskGetTool;
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
baseDir = await fs.mkdtemp(path.join(os.tmpdir(), 'renx-task-version-'));
|
|
23
|
+
store = new TaskStore({ baseDir });
|
|
24
|
+
taskCreate = new TaskCreateTool({ store });
|
|
25
|
+
taskUpdate = new TaskUpdateTool({ store });
|
|
26
|
+
taskGet = new TaskGetTool({ store });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
await fs.rm(baseDir, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('rejects stale expected_version and accepts correct one', async () => {
|
|
34
|
+
const task = parseOutput<{ task: { id: string; version: number } }>(
|
|
35
|
+
(
|
|
36
|
+
await taskCreate.execute({
|
|
37
|
+
namespace: 'ver1',
|
|
38
|
+
subject: 'Versioned task',
|
|
39
|
+
description: 'Versioned task for optimistic lock verification.',
|
|
40
|
+
})
|
|
41
|
+
).output
|
|
42
|
+
).task;
|
|
43
|
+
|
|
44
|
+
const stale = await taskUpdate.execute({
|
|
45
|
+
namespace: 'ver1',
|
|
46
|
+
task_id: task.id,
|
|
47
|
+
subject: 'Versioned task updated once',
|
|
48
|
+
expected_version: task.version + 1,
|
|
49
|
+
});
|
|
50
|
+
expect(stale.success).toBe(false);
|
|
51
|
+
expect(stale.output).toContain('TASK_VERSION_CONFLICT');
|
|
52
|
+
|
|
53
|
+
const ok = await taskUpdate.execute({
|
|
54
|
+
namespace: 'ver1',
|
|
55
|
+
task_id: task.id,
|
|
56
|
+
subject: 'Versioned task updated once',
|
|
57
|
+
expected_version: task.version,
|
|
58
|
+
});
|
|
59
|
+
expect(ok.success).toBe(true);
|
|
60
|
+
const okPayload = parseOutput<{ task: { version: number } }>(ok.output);
|
|
61
|
+
expect(okPayload.task.version).toBe(task.version + 1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('supports adding and removing dependencies and keeps graph symmetric', async () => {
|
|
65
|
+
const a = parseOutput<{ task: { id: string } }>(
|
|
66
|
+
(
|
|
67
|
+
await taskCreate.execute({
|
|
68
|
+
namespace: 'ver2',
|
|
69
|
+
subject: 'Task A',
|
|
70
|
+
description: 'Task A detail for dependency symmetry test.',
|
|
71
|
+
})
|
|
72
|
+
).output
|
|
73
|
+
).task;
|
|
74
|
+
const b = parseOutput<{ task: { id: string } }>(
|
|
75
|
+
(
|
|
76
|
+
await taskCreate.execute({
|
|
77
|
+
namespace: 'ver2',
|
|
78
|
+
subject: 'Task B',
|
|
79
|
+
description: 'Task B detail for dependency symmetry test.',
|
|
80
|
+
})
|
|
81
|
+
).output
|
|
82
|
+
).task;
|
|
83
|
+
|
|
84
|
+
const added = await taskUpdate.execute({
|
|
85
|
+
namespace: 'ver2',
|
|
86
|
+
task_id: b.id,
|
|
87
|
+
add_blocked_by: [a.id],
|
|
88
|
+
});
|
|
89
|
+
expect(added.success).toBe(true);
|
|
90
|
+
|
|
91
|
+
const afterAddA = parseOutput<{ task: { blocks: string[] } }>(
|
|
92
|
+
(await taskGet.execute({ namespace: 'ver2', task_id: a.id })).output
|
|
93
|
+
);
|
|
94
|
+
const afterAddB = parseOutput<{ task: { blockedBy: string[] } }>(
|
|
95
|
+
(await taskGet.execute({ namespace: 'ver2', task_id: b.id })).output
|
|
96
|
+
);
|
|
97
|
+
expect(afterAddA.task.blocks).toContain(b.id);
|
|
98
|
+
expect(afterAddB.task.blockedBy).toContain(a.id);
|
|
99
|
+
|
|
100
|
+
const removed = await taskUpdate.execute({
|
|
101
|
+
namespace: 'ver2',
|
|
102
|
+
task_id: b.id,
|
|
103
|
+
remove_blocked_by: [a.id],
|
|
104
|
+
});
|
|
105
|
+
expect(removed.success).toBe(true);
|
|
106
|
+
|
|
107
|
+
const afterRemoveA = parseOutput<{ task: { blocks: string[] } }>(
|
|
108
|
+
(await taskGet.execute({ namespace: 'ver2', task_id: a.id })).output
|
|
109
|
+
);
|
|
110
|
+
const afterRemoveB = parseOutput<{ task: { blockedBy: string[] } }>(
|
|
111
|
+
(await taskGet.execute({ namespace: 'ver2', task_id: b.id })).output
|
|
112
|
+
);
|
|
113
|
+
expect(afterRemoveA.task.blocks).not.toContain(b.id);
|
|
114
|
+
expect(afterRemoveB.task.blockedBy).not.toContain(a.id);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import * as os from 'node:os';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { TaskStore } from '../task-store';
|
|
6
|
+
import { TaskCreateTool } from '../task-create';
|
|
7
|
+
import { TaskGetTool } from '../task-get';
|
|
8
|
+
import { TaskListTool } from '../task-list';
|
|
9
|
+
import { TaskUpdateTool } from '../task-update';
|
|
10
|
+
|
|
11
|
+
function parseOutput<T>(output: string | undefined): T {
|
|
12
|
+
return JSON.parse(output || '{}') as T;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('task_create/task_get/task_list/task_update', () => {
|
|
16
|
+
let baseDir: string;
|
|
17
|
+
let store: TaskStore;
|
|
18
|
+
let taskCreate: TaskCreateTool;
|
|
19
|
+
let taskGet: TaskGetTool;
|
|
20
|
+
let taskList: TaskListTool;
|
|
21
|
+
let taskUpdate: TaskUpdateTool;
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
baseDir = await fs.mkdtemp(path.join(os.tmpdir(), 'renx-task-tools-'));
|
|
25
|
+
store = new TaskStore({ baseDir });
|
|
26
|
+
taskCreate = new TaskCreateTool({ store });
|
|
27
|
+
taskGet = new TaskGetTool({ store });
|
|
28
|
+
taskList = new TaskListTool({ store });
|
|
29
|
+
taskUpdate = new TaskUpdateTool({ store });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
await fs.rm(baseDir, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('creates a task and returns full task payload', async () => {
|
|
37
|
+
const result = await taskCreate.execute({
|
|
38
|
+
namespace: 'ns1',
|
|
39
|
+
subject: 'Implement feature X',
|
|
40
|
+
description: 'Implement feature X with tests and docs.',
|
|
41
|
+
priority: 'high',
|
|
42
|
+
metadata: { module: 'auth' },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(result.success).toBe(true);
|
|
46
|
+
const payload = parseOutput<{
|
|
47
|
+
namespace: string;
|
|
48
|
+
task: { id: string; status: string; version: number };
|
|
49
|
+
}>(result.output);
|
|
50
|
+
expect(payload.namespace).toBe('ns1');
|
|
51
|
+
expect(payload.task.id).toContain('task_');
|
|
52
|
+
expect(payload.task.status).toBe('pending');
|
|
53
|
+
expect(payload.task.version).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('rejects duplicate active subject', async () => {
|
|
57
|
+
await taskCreate.execute({
|
|
58
|
+
namespace: 'ns2',
|
|
59
|
+
subject: 'Same subject',
|
|
60
|
+
description: 'first task with same subject for duplicate detection',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const duplicate = await taskCreate.execute({
|
|
64
|
+
namespace: 'ns2',
|
|
65
|
+
subject: 'Same subject',
|
|
66
|
+
description: 'second task with same subject for duplicate detection',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(duplicate.success).toBe(false);
|
|
70
|
+
expect(duplicate.output).toContain('TASK_DUPLICATE_SUBJECT');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('computes can_start and blocker summaries in task_get', async () => {
|
|
74
|
+
const taskA = parseOutput<{ task: { id: string } }>(
|
|
75
|
+
(
|
|
76
|
+
await taskCreate.execute({
|
|
77
|
+
namespace: 'ns3',
|
|
78
|
+
subject: 'Prepare architecture',
|
|
79
|
+
description: 'Prepare architecture and contracts for implementation.',
|
|
80
|
+
})
|
|
81
|
+
).output
|
|
82
|
+
).task;
|
|
83
|
+
|
|
84
|
+
const taskB = parseOutput<{ task: { id: string } }>(
|
|
85
|
+
(
|
|
86
|
+
await taskCreate.execute({
|
|
87
|
+
namespace: 'ns3',
|
|
88
|
+
subject: 'Implement API',
|
|
89
|
+
description: 'Implement API based on architecture and contracts.',
|
|
90
|
+
})
|
|
91
|
+
).output
|
|
92
|
+
).task;
|
|
93
|
+
|
|
94
|
+
const addDependency = await taskUpdate.execute({
|
|
95
|
+
namespace: 'ns3',
|
|
96
|
+
task_id: taskB.id,
|
|
97
|
+
add_blocked_by: [taskA.id],
|
|
98
|
+
});
|
|
99
|
+
expect(addDependency.success).toBe(true);
|
|
100
|
+
|
|
101
|
+
const detail = await taskGet.execute({
|
|
102
|
+
namespace: 'ns3',
|
|
103
|
+
task_id: taskB.id,
|
|
104
|
+
});
|
|
105
|
+
expect(detail.success).toBe(true);
|
|
106
|
+
|
|
107
|
+
const payload = parseOutput<{
|
|
108
|
+
task: {
|
|
109
|
+
can_start: { canStart: boolean; reason?: string };
|
|
110
|
+
blockers: Array<{ id: string }>;
|
|
111
|
+
};
|
|
112
|
+
}>(detail.output);
|
|
113
|
+
|
|
114
|
+
expect(payload.task.can_start.canStart).toBe(false);
|
|
115
|
+
expect(payload.task.can_start.reason).toContain(taskA.id);
|
|
116
|
+
expect(payload.task.blockers.map((item) => item.id)).toContain(taskA.id);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('detects cycle dependency on update', async () => {
|
|
120
|
+
const taskA = parseOutput<{ task: { id: string } }>(
|
|
121
|
+
(
|
|
122
|
+
await taskCreate.execute({
|
|
123
|
+
namespace: 'ns4',
|
|
124
|
+
subject: 'Task A',
|
|
125
|
+
description: 'Task A detail with enough description text.',
|
|
126
|
+
})
|
|
127
|
+
).output
|
|
128
|
+
).task;
|
|
129
|
+
const taskB = parseOutput<{ task: { id: string } }>(
|
|
130
|
+
(
|
|
131
|
+
await taskCreate.execute({
|
|
132
|
+
namespace: 'ns4',
|
|
133
|
+
subject: 'Task B',
|
|
134
|
+
description: 'Task B detail with enough description text.',
|
|
135
|
+
})
|
|
136
|
+
).output
|
|
137
|
+
).task;
|
|
138
|
+
|
|
139
|
+
expect(
|
|
140
|
+
(
|
|
141
|
+
await taskUpdate.execute({
|
|
142
|
+
namespace: 'ns4',
|
|
143
|
+
task_id: taskB.id,
|
|
144
|
+
add_blocked_by: [taskA.id],
|
|
145
|
+
})
|
|
146
|
+
).success
|
|
147
|
+
).toBe(true);
|
|
148
|
+
|
|
149
|
+
const cycle = await taskUpdate.execute({
|
|
150
|
+
namespace: 'ns4',
|
|
151
|
+
task_id: taskA.id,
|
|
152
|
+
add_blocked_by: [taskB.id],
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(cycle.success).toBe(false);
|
|
156
|
+
expect(cycle.output).toContain('TASK_CYCLE_DEPENDENCY');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('supports owner=null and records status transition history accurately', async () => {
|
|
160
|
+
const created = parseOutput<{ task: { id: string } }>(
|
|
161
|
+
(
|
|
162
|
+
await taskCreate.execute({
|
|
163
|
+
namespace: 'ns5',
|
|
164
|
+
subject: 'Run migration',
|
|
165
|
+
description: 'Run migration with rollback plan and checklist.',
|
|
166
|
+
})
|
|
167
|
+
).output
|
|
168
|
+
).task;
|
|
169
|
+
|
|
170
|
+
const update1 = await taskUpdate.execute({
|
|
171
|
+
namespace: 'ns5',
|
|
172
|
+
task_id: created.id,
|
|
173
|
+
status: 'in_progress',
|
|
174
|
+
owner: 'main-agent',
|
|
175
|
+
updated_by: 'tester',
|
|
176
|
+
});
|
|
177
|
+
expect(update1.success).toBe(true);
|
|
178
|
+
|
|
179
|
+
const release = await taskUpdate.execute({
|
|
180
|
+
namespace: 'ns5',
|
|
181
|
+
task_id: created.id,
|
|
182
|
+
owner: null,
|
|
183
|
+
updated_by: 'tester',
|
|
184
|
+
});
|
|
185
|
+
expect(release.success).toBe(true);
|
|
186
|
+
|
|
187
|
+
const complete = await taskUpdate.execute({
|
|
188
|
+
namespace: 'ns5',
|
|
189
|
+
task_id: created.id,
|
|
190
|
+
status: 'completed',
|
|
191
|
+
updated_by: 'tester',
|
|
192
|
+
});
|
|
193
|
+
expect(complete.success).toBe(true);
|
|
194
|
+
|
|
195
|
+
const detail = await taskGet.execute({
|
|
196
|
+
namespace: 'ns5',
|
|
197
|
+
task_id: created.id,
|
|
198
|
+
include_history: true,
|
|
199
|
+
});
|
|
200
|
+
const payload = parseOutput<{
|
|
201
|
+
task: {
|
|
202
|
+
owner: string | null;
|
|
203
|
+
history: Array<{ action: string; fromStatus?: string; toStatus?: string }>;
|
|
204
|
+
};
|
|
205
|
+
}>(detail.output);
|
|
206
|
+
expect(payload.task.owner).toBeNull();
|
|
207
|
+
|
|
208
|
+
const statusEntries = payload.task.history.filter((entry) => entry.action === 'status_changed');
|
|
209
|
+
expect(statusEntries.length).toBe(2);
|
|
210
|
+
expect(statusEntries[0]).toMatchObject({
|
|
211
|
+
fromStatus: 'pending',
|
|
212
|
+
toStatus: 'in_progress',
|
|
213
|
+
});
|
|
214
|
+
expect(statusEntries[1]).toMatchObject({
|
|
215
|
+
fromStatus: 'in_progress',
|
|
216
|
+
toStatus: 'completed',
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('sorts list with critical claimable before in_progress and others', async () => {
|
|
221
|
+
const critical = parseOutput<{ task: { id: string } }>(
|
|
222
|
+
(
|
|
223
|
+
await taskCreate.execute({
|
|
224
|
+
namespace: 'ns6',
|
|
225
|
+
subject: 'Emergency fix',
|
|
226
|
+
description: 'Emergency fix in production with immediate response.',
|
|
227
|
+
priority: 'critical',
|
|
228
|
+
})
|
|
229
|
+
).output
|
|
230
|
+
).task;
|
|
231
|
+
|
|
232
|
+
const normal = parseOutput<{ task: { id: string } }>(
|
|
233
|
+
(
|
|
234
|
+
await taskCreate.execute({
|
|
235
|
+
namespace: 'ns6',
|
|
236
|
+
subject: 'Regular task',
|
|
237
|
+
description: 'Regular task for planned sprint implementation.',
|
|
238
|
+
priority: 'normal',
|
|
239
|
+
})
|
|
240
|
+
).output
|
|
241
|
+
).task;
|
|
242
|
+
|
|
243
|
+
const inProgress = parseOutput<{ task: { id: string } }>(
|
|
244
|
+
(
|
|
245
|
+
await taskCreate.execute({
|
|
246
|
+
namespace: 'ns6',
|
|
247
|
+
subject: 'In-progress task',
|
|
248
|
+
description: 'Task that will be moved to in progress.',
|
|
249
|
+
priority: 'high',
|
|
250
|
+
})
|
|
251
|
+
).output
|
|
252
|
+
).task;
|
|
253
|
+
|
|
254
|
+
await taskUpdate.execute({
|
|
255
|
+
namespace: 'ns6',
|
|
256
|
+
task_id: inProgress.id,
|
|
257
|
+
status: 'in_progress',
|
|
258
|
+
owner: 'agent-1',
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const listed = await taskList.execute({ namespace: 'ns6' });
|
|
262
|
+
expect(listed.success).toBe(true);
|
|
263
|
+
const payload = parseOutput<{ tasks: Array<{ id: string }> }>(listed.output);
|
|
264
|
+
expect(payload.tasks[0].id).toBe(critical.id);
|
|
265
|
+
expect(payload.tasks.map((item) => item.id)).toContain(normal.id);
|
|
266
|
+
});
|
|
267
|
+
});
|